From 5281db5ce4c05cc71addc73d90a11060c46793ad Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Fri, 18 Aug 2023 07:29:11 +0200 Subject: [PATCH 001/462] Also update javadoc. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d6cde8a..511b2fe 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,4 +33,4 @@ jobs: java-version: '17' distribution: 'temurin' - name: Push with Gradle - run: ./gradlew -Pdocker.registry=ghcr.io/${{ github.actor }} pushImages + run: ./gradlew -Pdocker.registry=ghcr.io/${{ github.actor }} stage pushImages From 823bcedf1ede55f322f66dab833fc9f72c2b94d9 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 18 Aug 2023 11:23:17 +0200 Subject: [PATCH 002/462] Change configuration format. --- dev-example/config.yaml | 13 +++--- example/local-path/kustomization.yaml | 7 ++-- example/rook-ceph/kustomization.yaml | 5 ++- .../config-sample.yaml | 42 ++++++++++--------- .../vmoperator/manager/runnerSts.ftl.yaml | 12 +++--- ...conciler.java => ConfigMapReconciler.java} | 4 +- .../manager/LoadBalancerReconciler.java | 2 +- .../vmoperator/manager/Reconciler.java | 18 ++++---- ...ciler.java => StatefuleSetReconciler.java} | 4 +- 9 files changed, 55 insertions(+), 52 deletions(-) rename org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/{CmReconciler.java => ConfigMapReconciler.java} (98%) rename org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/{StsReconciler.java => StatefuleSetReconciler.java} (97%) diff --git a/dev-example/config.yaml b/dev-example/config.yaml index cafac06..6b1e824 100644 --- a/dev-example/config.yaml +++ b/dev-example/config.yaml @@ -3,9 +3,10 @@ "/Manager": "/Controller": namespace: vmop-dev - runnerData: - storageClassName: null - loadBalancerService: | - labels: - test1: info - test2: toBeDeleted + "/Reconciler": + runnerData: + storageClassName: null + loadBalancerService: | + labels: + test1: info + test2: toBeDeleted diff --git a/example/local-path/kustomization.yaml b/example/local-path/kustomization.yaml index 799712a..84324c8 100644 --- a/example/local-path/kustomization.yaml +++ b/example/local-path/kustomization.yaml @@ -32,6 +32,7 @@ patches: config.yaml: | "/Manager": "/Controller": - runnerData: - # Default is to use the default storage class - storageClassName: local-path + "/Reconciler" + runnerDataPvc: + # Default is to use the default storage class + storageClassName: local-path diff --git a/example/rook-ceph/kustomization.yaml b/example/rook-ceph/kustomization.yaml index 495fe3c..0d8477f 100644 --- a/example/rook-ceph/kustomization.yaml +++ b/example/rook-ceph/kustomization.yaml @@ -26,5 +26,6 @@ patches: config.yaml: | "/Manager": "/Controller": - runnerData: - storageClassName: rook-cephfs + "/Reconciler": + runnerData: + storageClassName: rook-cephfs diff --git a/org.jdrupes.vmoperator.manager/config-sample.yaml b/org.jdrupes.vmoperator.manager/config-sample.yaml index 68f28f5..56cfefc 100644 --- a/org.jdrupes.vmoperator.manager/config-sample.yaml +++ b/org.jdrupes.vmoperator.manager/config-sample.yaml @@ -2,25 +2,27 @@ "/Manager": "/Controller": - # Values used when creating the PVC for the runner's data - runnerData: - storageClassName: null - - # Amount by which the current cpu count is devided when generating - # the resource properties. - cpuOvercommit: 2 - - # Amount by which the current ram size is devided when generating - # the resource properties. - ramOvercommit: 1.5 - - # If defined, causes a load balancer service to be created. - # May be a boolean or a string with nested yaml that - # defines additional labels or annotations to be merged - # into the service. - # loadBalancerService: | - # labels: {} - # annotations: {} - # Explicitly specify the namespace to be managed (only for development). # namespace: vmop-dev + + "/Reconciler": + # Amount by which the current cpu count is devided when generating + # the resource properties. + cpuOvercommit: 2 + + # Amount by which the current ram size is devided when generating + # the resource properties. + ramOvercommit: 1.5 + + # Values used when creating the PVC for the runner's data + runnerDataPvc: + storageClassName: null + + # If defined, causes a load balancer service to be created. + # May be a boolean or a string with nested yaml that + # defines additional labels or annotations to be merged + # into the service. + # loadBalancerService: | + # labels: {} + # annotations: {} + diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml index 8bfdfb5..f870ae7 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml @@ -96,15 +96,15 @@ spec: requests: <#if cr.spec.vm.currentCpus?? > <#assign factor = 2.0 /> - <#if config.cpuOvercommit??> - <#assign factor = config.cpuOvercommit * 1.0 /> + <#if reconciler.cpuOvercommit??> + <#assign factor = reconciler.cpuOvercommit * 1.0 /> cpu: ${ (cr.spec.vm.currentCpus.asInt / factor)?floor?c } <#if cr.spec.vm.currentRam?? > <#assign factor = 1.25 /> - <#if config.ramOvercommit??> - <#assign factor = config.ramOvercommit * 1.0 /> + <#if reconciler.ramOvercommit??> + <#assign factor = reconciler.ramOvercommit * 1.0 /> memory: ${ (parseMemory(cr.spec.vm.currentRam.asString) / factor)?floor?c } @@ -151,8 +151,8 @@ spec: spec: accessModes: - ReadWriteOnce - <#if config.runnerData?? && config.runnerData.storageClassName??> - storageClassName: ${ config.runnerData.storageClassName } + <#if reconciler.runnerDataPvc?? && reconciler.runnerDataPvc.storageClassName??> + storageClassName: ${ reconciler.runnerDataPvc.storageClassName } resources: requests: diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/CmReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java similarity index 98% rename from org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/CmReconciler.java rename to org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java index 5475ed3..340a3cd 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/CmReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java @@ -40,7 +40,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; * Delegee for reconciling the config map */ @SuppressWarnings("PMD.DataflowAnomalyAnalysis") -/* default */ class CmReconciler { +/* default */ class ConfigMapReconciler { protected final Logger logger = Logger.getLogger(getClass().getName()); private final Configuration fmConfig; @@ -50,7 +50,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; * * @param fmConfig the fm config */ - public CmReconciler(Configuration fmConfig) { + public ConfigMapReconciler(Configuration fmConfig) { this.fmConfig = fmConfig; } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java index f0dd304..8cf8f63 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java @@ -77,7 +77,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; // Check if to be generated @SuppressWarnings("unchecked") var lbs = Optional.of(model) - .map(m -> (Map) m.get("config")) + .map(m -> (Map) m.get("reconciler")) .map(c -> c.get(LOAD_BALANCER_SERVICE)).orElse(Boolean.FALSE); if (lbs instanceof Boolean isOn && !isOn) { return; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index def68f6..eb91e2f 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -45,7 +45,6 @@ import org.jdrupes.vmoperator.util.ExtendedObjectWrapper; import org.jdrupes.vmoperator.util.ParseUtils; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; -import org.jgrapes.core.Components; import org.jgrapes.core.annotation.Handler; import org.jgrapes.util.events.ConfigurationUpdate; @@ -58,8 +57,8 @@ public class Reconciler extends Component { @SuppressWarnings("PMD.SingularField") private final Configuration fmConfig; - private final CmReconciler cmReconciler; - private final StsReconciler stsReconciler; + private final ConfigMapReconciler cmReconciler; + private final StatefuleSetReconciler stsReconciler; private final LoadBalancerReconciler lbReconciler; @SuppressWarnings("PMD.UseConcurrentHashMap") private final Map config = new HashMap<>(); @@ -82,8 +81,8 @@ public class Reconciler extends Component { fmConfig.setLogTemplateExceptions(false); fmConfig.setClassForTemplateLoading(Reconciler.class, ""); - cmReconciler = new CmReconciler(fmConfig); - stsReconciler = new StsReconciler(fmConfig); + cmReconciler = new ConfigMapReconciler(fmConfig); + stsReconciler = new StatefuleSetReconciler(fmConfig); lbReconciler = new LoadBalancerReconciler(fmConfig); } @@ -94,10 +93,9 @@ public class Reconciler extends Component { */ @Handler public void onConfigurationUpdate(ConfigurationUpdate event) { - event.structured(Components.manager(parent()).componentPath()) - .ifPresent(c -> { - config.putAll(c); - }); + event.structured(componentPath()).ifPresent(c -> { + config.putAll(c); + }); } /** @@ -153,7 +151,7 @@ public class Reconciler extends Component { Configuration.VERSION_2_3_32) .build().getStaticModels() .get(Constants.class.getName())); - model.put("config", config); + model.put("reconciler", config); // Methods model.put("parseMemory", new TemplateMethodModelEx() { diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StsReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefuleSetReconciler.java similarity index 97% rename from org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StsReconciler.java rename to org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefuleSetReconciler.java index 42fcb23..6f6d09c 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StsReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefuleSetReconciler.java @@ -37,7 +37,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; * Delegee for reconciling the stateful set (effectively the pod). */ @SuppressWarnings("PMD.DataflowAnomalyAnalysis") -/* default */ class StsReconciler { +/* default */ class StatefuleSetReconciler { protected final Logger logger = Logger.getLogger(getClass().getName()); private final Configuration fmConfig; @@ -47,7 +47,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; * * @param fmConfig the fm config */ - public StsReconciler(Configuration fmConfig) { + public StatefuleSetReconciler(Configuration fmConfig) { this.fmConfig = fmConfig; } From 365bdf249db3887797d59458fdf2d50f2ed7ee6b Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 18 Aug 2023 14:09:02 +0200 Subject: [PATCH 003/462] Improve documentation. --- .../config-sample.yaml | 28 -------- .../vmoperator/manager/Controller.java | 32 +++++++++ .../jdrupes/vmoperator/manager/Manager.java | 25 ++++++- .../vmoperator/manager/Reconciler.java | 69 ++++++++++++++++++- 4 files changed, 123 insertions(+), 31 deletions(-) delete mode 100644 org.jdrupes.vmoperator.manager/config-sample.yaml diff --git a/org.jdrupes.vmoperator.manager/config-sample.yaml b/org.jdrupes.vmoperator.manager/config-sample.yaml deleted file mode 100644 index 56cfefc..0000000 --- a/org.jdrupes.vmoperator.manager/config-sample.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# The values in comments are the defaults. - -"/Manager": - "/Controller": - # Explicitly specify the namespace to be managed (only for development). - # namespace: vmop-dev - - "/Reconciler": - # Amount by which the current cpu count is devided when generating - # the resource properties. - cpuOvercommit: 2 - - # Amount by which the current ram size is devided when generating - # the resource properties. - ramOvercommit: 1.5 - - # Values used when creating the PVC for the runner's data - runnerDataPvc: - storageClassName: null - - # If defined, causes a load balancer service to be created. - # May be a boolean or a string with nested yaml that - # defines additional labels or annotations to be merged - # into the service. - # loadBalancerService: | - # labels: {} - # annotations: {} - diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java index fe668cb..998dc1d 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java @@ -29,6 +29,38 @@ import org.jgrapes.core.events.Start; /** * Implements a controller as defined in the * [Operator Whitepaper](https://github.com/cncf/tag-app-delivery/blob/eece8f7307f2970f46f100f51932db106db46968/operator-wg/whitepaper/Operator-WhitePaper_v1-0.md#operator-components-in-kubernetes). + * + * The implementation splits the controller in two components. The + * {@link VmWatcher} and the {@link Reconciler}. The former watches + * the VM definitions (CRs) and generates {@link VmDefChanged} events + * when they change. The latter handles the changes and reconciles the + * resources in the cluster. + * + * The controller itself supports a single configuration property: + * ```yaml + * "/Manager": + * "/Controller": + * namespace: vmop-dev + * ``` + * This may only be set when running the Manager (and thus the Controller) + * outside a container during development. + * + * ![Controller components](controller-components.svg) + * + * @startuml controller-components.svg + * skinparam component { + * BackGroundColor #FEFECE + * BorderColor #A80036 + * BorderThickness 1.25 + * BackgroundColor<> #F1F1F1 + * BorderColor<> #181818 + * BorderThickness<> 1 + * } + * + * [Controller] + * [Controller] *--> [VmWatcher] + * [Controller] *--> [Reconciler] + * @enduml */ public class Controller extends Component { diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java index 1dcd5c8..fed9740 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java @@ -42,7 +42,30 @@ import org.jgrapes.util.YamlConfigurationStore; import org.jgrapes.util.events.WatchFile; /** - * The application class. + * The application class. In framework term, this is the root component. + * Two of its child components, the {@link Controller} and the WebGui + * implement user-visible functions. The others are used internally. + * + * ![Manager components](manager-components.svg) + * + * @startuml manager-components.svg + * skinparam component { + * BackGroundColor #FEFECE + * BorderColor #A80036 + * BorderThickness 1.25 + * BackgroundColor<> #F1F1F1 + * BorderColor<> #181818 + * BorderThickness<> 1 + * } + * + * [Manager] + * [Manager] *--> [Controller] + * [Manager] *--> [WebGui] + * [NioDispatcher] <> + * [Manager] *--> [NioDispatcher] <> + * [FileSystemWatcher] <> + * [Manager] *--> [FileSystemWatcher] <> + * @enduml */ public class Manager extends Component { diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index eb91e2f..0608f10 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -49,7 +49,72 @@ import org.jgrapes.core.annotation.Handler; import org.jgrapes.util.events.ConfigurationUpdate; /** - * Adapts Kubenetes resources to changes in VM definitions (CRs). + * Adapts Kubenetes resources for instances of the Runner + * application (the VMs) to changes in VM definitions (the CRs). + * + * In particular, the reconciler generates and updates: + * + * * A [`PVC`](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) + * for storage used by all VMs as a common repository for CDROM images. + * + * * A [`ConfigMap`](https://kubernetes.io/docs/concepts/configuration/configmap/) + * that defines the configuration file for the runner. + * + * * A [`StatefulSet`](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/) + * that creates + * * the [`Pod`](https://kubernetes.io/docs/concepts/workloads/pods/) + * with the Runner instance, + * * a PVC for 1 MiB of persistent storage used by the Runner + * (referred to as the "runnerDataPvc") and + * * the PVCs for the VM's disks. + * + * * (Optional) A load balancer + * [`Service`](https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/) + * that allows the user to access a VM's console without knowing which + * node it runs on. + * + * The reconciler is part of the {@link Controller} component. It's + * configuration properties are therefore defined in + * ```yaml + * "/Manager": + * "/Controller": + * "/Reconciler": + * ... + * ``` + * + * The reconciler supports the following configuration properties: + * + * * `runnerDataPvc.storageClassName`: The storage class name + * to be used for the "runnerDataPvc" (the small volume used + * by the runner for information such as the EFI variables). By + * default, no `storageClassName` is generated, which causes + * Kubernetes to use storage from the default storage class. + * Define this if you want to use a specific storage class. + * + * * `cpuOvercommit`: The amount by which the current cpu count + * from the VM definition is divided when generating the + * [`resources`](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#resources) + * properties for the VM (defaults to 2). + * + * * `ramOvercommit`: The amount by which the current ram size + * from the VM definition is divided when generating the + * [`resources`](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#resources) + * properties for the VM (defaults to 1.25). + * + * * `loadBalancerService`: If defined, causes a load balancer service + * to be created. This property may be a boolean or a string with nested + * YAML that defines additional labels or annotations to be merged + * into the service defintion. Here's an example for using + * [MetalLb](https://metallb.universe.tf/) as "internal load balancer": + * ```yaml + * loadBalancerService: | + * annotations: + * metallb.universe.tf/loadBalancerIPs: 192.168.168.1 + * metallb.universe.tf/ip-allocated-from-pool: single-common + * metallb.universe.tf/allow-shared-ip: single-common + * ``` + * This make all VM consoles available at IP address 192.168.168.1 + * with the port numbers from the VM definitions. */ @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.AvoidDuplicateLiterals" }) @@ -87,7 +152,7 @@ public class Reconciler extends Component { } /** - * Configure the component. + * Configures the component. * * @param event the event */ From 3296ed84d497e193ddeadf8b68ed96e296632dad Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 18 Aug 2023 14:24:19 +0200 Subject: [PATCH 004/462] Adapt regexp. --- deploy/crds/vms-crd.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index b151a4a..7a3162e 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -56,7 +56,7 @@ spec: anyOf: - type: integer - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + pattern: ^\s*(\d+(\.\d+)?)\s*(B|kB|MB|GB|TB|PB|EB|KiB|MiB|GiB|TiB|PiB|EiB|Ki|Mi|Gi|Ti|Pi|Ei)?$ x-kubernetes-int-or-string: true description: >- Limits describes the maximum amount of compute @@ -67,7 +67,7 @@ spec: anyOf: - type: integer - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + pattern: ^\s*(\d+(\.\d+)?)\s*(B|kB|MB|GB|TB|PB|EB|KiB|MiB|GiB|TiB|PiB|EiB|Ki|Mi|Gi|Ti|Pi|Ei)?$ x-kubernetes-int-or-string: true description: >- Requests describes the minimum amount of compute From 3968c36e3562782e4bda7c0685341a86bd9ee4e2 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 18 Aug 2023 14:50:45 +0200 Subject: [PATCH 005/462] Update description. --- deploy/crds/vms-crd.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index 7a3162e..406a5cf 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -48,7 +48,13 @@ spec: type: string resources: description: >- - Compute Resources required by this VM. + Define the Compute Resources required by this VM. + This overrides the automnatically generated defaults. + Note that the format has been defined differently from + the usage in Kubernetes. "m" is not supported because + of its known ambiguity (and "0.4" is shorter than "400m" + anyway). Single letter memory sizes are ambiguous as + well (expect for "B"). type: object properties: limits: From 0e52acfdef5d823106c395e9759f08ba43337f4b Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 18 Aug 2023 15:33:54 +0200 Subject: [PATCH 006/462] Can, maybe, be simplified. --- .../src/org.jdrupes.vmoperator.java-doc-conventions.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle index db5e742..1012df8 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle @@ -105,8 +105,8 @@ gitPublish { from("${rootProject.buildDir}/javadoc") { into 'javadoc' } - if ({ !findProject(':org.jdrupes.vmoperator.runner.qemu').isSnapshot - && !findProject(':org.jdrupes.vmoperator.manager').isSnapshot }) { + if (!findProject(':org.jdrupes.vmoperator.runner.qemu').isSnapshot + && !findProject(':org.jdrupes.vmoperator.manager').isSnapshot) { from("${rootProject.buildDir}/javadoc") { into 'latest-release/javadoc' } From 0ba499640886c0fc96abe2f360a977c7a190db2d Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 18 Aug 2023 15:34:09 +0200 Subject: [PATCH 007/462] Fix typo. --- .../src/org/jdrupes/vmoperator/manager/Reconciler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index 0608f10..efba4d4 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -113,7 +113,7 @@ import org.jgrapes.util.events.ConfigurationUpdate; * metallb.universe.tf/ip-allocated-from-pool: single-common * metallb.universe.tf/allow-shared-ip: single-common * ``` - * This make all VM consoles available at IP address 192.168.168.1 + * This makes all VM consoles available at IP address 192.168.168.1 * with the port numbers from the VM definitions. */ @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", From 1a5c4ee639f7a5632414817513a28484ac28cb4a Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 18 Aug 2023 15:41:29 +0200 Subject: [PATCH 008/462] Does doc generation as well. --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 511b2fe..e91d507 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,10 @@ permissions: contents: read packages: write +concurrency: + group: doc_generation + cancel-in-progress: false + jobs: build: From 3297ecd034fa3785b00b7c9abf6324942808f9e7 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 18 Aug 2023 15:56:20 +0200 Subject: [PATCH 009/462] Add missing token. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e91d507..3c59936 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,4 +37,4 @@ jobs: java-version: '17' distribution: 'temurin' - name: Push with Gradle - run: ./gradlew -Pdocker.registry=ghcr.io/${{ github.actor }} stage pushImages + run: ./gradlew -Prepo.access.token=${{ secrets.REPO_ACCESS_TOKEN }} -Pdocker.registry=ghcr.io/${{ github.actor }} stage pushImages From 7141d381be65e3b982116af1e41a46572e660203 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 19 Aug 2023 10:44:54 +0200 Subject: [PATCH 010/462] Drop support for Kubernetes abbreviation. --- .../src/org/jdrupes/vmoperator/util/ParseUtils.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/ParseUtils.java b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/ParseUtils.java index ff2ab31..8226c18 100644 --- a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/ParseUtils.java +++ b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/ParseUtils.java @@ -26,7 +26,7 @@ import java.util.Map; import java.util.regex.Pattern; /** - * Provides methods for parsing. + * Provides methods for parsing "official" memory sizes.. */ @SuppressWarnings("PMD.UseUtilityClass") public class ParseUtils { @@ -53,7 +53,6 @@ public class ParseUtils { scale = BigInteger.valueOf(1024); for (var unit : List.of("KiB", "MiB", "GiB", "TiB", "PiB", "EiB")) { unitMap.put(unit, factor); - unitMap.put(unit.substring(0, 2), factor); factor = factor.multiply(scale); } } From 8461adbe7c58f2bf4ae37a01e205bf9c75926c33 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 19 Aug 2023 10:46:12 +0200 Subject: [PATCH 011/462] Use Kubernetes format everywhere for consistency. --- deploy/crds/vms-crd.yaml | 4 ++-- dev-example/test-vm.yaml | 4 ++-- example/local-path/test-vm.yaml | 4 ++-- example/rook-ceph/test-vm.yaml | 4 ++-- .../org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml | 4 ++-- .../src/org/jdrupes/vmoperator/manager/Reconciler.java | 6 +++--- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index 406a5cf..b40dc8c 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -62,7 +62,7 @@ spec: anyOf: - type: integer - type: string - pattern: ^\s*(\d+(\.\d+)?)\s*(B|kB|MB|GB|TB|PB|EB|KiB|MiB|GiB|TiB|PiB|EiB|Ki|Mi|Gi|Ti|Pi|Ei)?$ + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true description: >- Limits describes the maximum amount of compute @@ -73,7 +73,7 @@ spec: anyOf: - type: integer - type: string - pattern: ^\s*(\d+(\.\d+)?)\s*(B|kB|MB|GB|TB|PB|EB|KiB|MiB|GiB|TiB|PiB|EiB|Ki|Mi|Gi|Ti|Pi|Ei)?$ + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ x-kubernetes-int-or-string: true description: >- Requests describes the minimum amount of compute diff --git a/dev-example/test-vm.yaml b/dev-example/test-vm.yaml index d3f4123..a60ef59 100644 --- a/dev-example/test-vm.yaml +++ b/dev-example/test-vm.yaml @@ -17,8 +17,8 @@ spec: vm: # state: Running bootMenu: yes - maximumRam: "8 GiB" - currentRam: "4 GiB" + maximumRam: 8Gi + currentRam: 4Gi maximumCpus: 4 currentCpus: 4 diff --git a/example/local-path/test-vm.yaml b/example/local-path/test-vm.yaml index c6f3011..49e22ae 100644 --- a/example/local-path/test-vm.yaml +++ b/example/local-path/test-vm.yaml @@ -15,8 +15,8 @@ spec: vm: maximumCpus: 4 currentCpus: 2 - maximumRam: "8 GiB" - currentRam: "4 GiB" + maximumRam: 8Gi + currentRam: 4Gi networks: - user: {} diff --git a/example/rook-ceph/test-vm.yaml b/example/rook-ceph/test-vm.yaml index 2dc71e0..1222be3 100644 --- a/example/rook-ceph/test-vm.yaml +++ b/example/rook-ceph/test-vm.yaml @@ -15,8 +15,8 @@ spec: vm: maximumCpus: 4 currentCpus: 2 - maximumRam: "8 GiB" - currentRam: "4 GiB" + maximumRam: 8Gi + currentRam: 4Gi networks: - user: {} diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml index f870ae7..053e1c3 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml @@ -99,14 +99,14 @@ spec: <#if reconciler.cpuOvercommit??> <#assign factor = reconciler.cpuOvercommit * 1.0 /> - cpu: ${ (cr.spec.vm.currentCpus.asInt / factor)?floor?c } + cpu: ${ (parseQuantity(cr.spec.vm.currentCpus.asString) / factor)?floor?c } <#if cr.spec.vm.currentRam?? > <#assign factor = 1.25 /> <#if reconciler.ramOvercommit??> <#assign factor = reconciler.ramOvercommit * 1.0 /> - memory: ${ (parseMemory(cr.spec.vm.currentRam.asString) / factor)?floor?c } + memory: ${ (parseQuantity(cr.spec.vm.currentRam.asString) / factor)?floor?c } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index efba4d4..f135a7e 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -30,6 +30,7 @@ import freemarker.template.TemplateHashModel; import freemarker.template.TemplateMethodModelEx; import freemarker.template.TemplateModelException; import freemarker.template.TemplateNotFoundException; +import io.kubernetes.client.custom.Quantity; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; import java.io.IOException; @@ -42,7 +43,6 @@ import java.util.Optional; import static org.jdrupes.vmoperator.manager.Constants.VM_OP_GROUP; import org.jdrupes.vmoperator.manager.VmDefChanged.Type; import org.jdrupes.vmoperator.util.ExtendedObjectWrapper; -import org.jdrupes.vmoperator.util.ParseUtils; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.annotation.Handler; @@ -219,7 +219,7 @@ public class Reconciler extends Component { model.put("reconciler", config); // Methods - model.put("parseMemory", new TemplateMethodModelEx() { + model.put("parseQuantity", new TemplateMethodModelEx() { @Override @SuppressWarnings("PMD.PreserveStackTrace") public Object exec(@SuppressWarnings("rawtypes") List arguments) @@ -229,7 +229,7 @@ public class Reconciler extends Component { return number; } try { - return ParseUtils.parseMemory(arg.toString()); + return Quantity.fromString(arg.toString()).getNumber(); } catch (NumberFormatException e) { throw new TemplateModelException("Cannot parse memory " + "specified as \"" + arg + "\": " + e.getMessage()); From c006bd0a39da92dde69f2cfcac259623f7a32db7 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 19 Aug 2023 10:51:19 +0200 Subject: [PATCH 012/462] Extend test. --- dev-example/test-vm.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dev-example/test-vm.yaml b/dev-example/test-vm.yaml index a60ef59..500efd2 100644 --- a/dev-example/test-vm.yaml +++ b/dev-example/test-vm.yaml @@ -9,6 +9,11 @@ spec: path: vmoperator/org.jdrupes.vmoperator.runner.qemu-arch pullPolicy: Always + resources: + requests: + cpu: 1 + memory: 2Gi + loadBalancerService: labels: test2: null From 6c98438fc534c5beb6a689eab6f66f0d7ddf3b71 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 19 Aug 2023 15:22:31 +0200 Subject: [PATCH 013/462] Basics for automated test. --- dev-example/Readme.md | 4 + dev-example/config.yaml | 8 +- dev-example/kustomization.yaml | 15 ++++ dev-example/test-vm.yaml | 5 -- org.jdrupes.vmoperator.manager/build.gradle | 17 ++++ .../test-resources/unittest-vm.yaml | 35 ++++++++ .../vmoperator/manager/BasicTests.java | 81 +++++++++++++++++++ .../vmoperator/manager/package-info.java | 1 + .../.settings/org.eclipse.jdt.core.prefs | 10 +-- 9 files changed, 160 insertions(+), 16 deletions(-) create mode 100644 org.jdrupes.vmoperator.manager/test-resources/unittest-vm.yaml create mode 100644 org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java create mode 100644 org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/package-info.java diff --git a/dev-example/Readme.md b/dev-example/Readme.md index c8e40eb..99f8d0f 100644 --- a/dev-example/Readme.md +++ b/dev-example/Readme.md @@ -13,3 +13,7 @@ The `kustomize.yaml` also changes the image repository for the operator to a private repository for development. You have to either remove this or adapt it to your own repository if you also want to test your development version in a container. + +If you want to run the unittests, this setup must be run with a private +repository and the private repository must match the one configured +for gradle pushImages. \ No newline at end of file diff --git a/dev-example/config.yaml b/dev-example/config.yaml index 6b1e824..6f380de 100644 --- a/dev-example/config.yaml +++ b/dev-example/config.yaml @@ -1,12 +1,8 @@ -# The values in comments are the defaults. - +# Used for running manager outside Kubernetes. +# Keep in sync with kustomize.yaml "/Manager": "/Controller": namespace: vmop-dev "/Reconciler": runnerData: storageClassName: null - loadBalancerService: | - labels: - test1: info - test2: toBeDeleted diff --git a/dev-example/kustomization.yaml b/dev-example/kustomization.yaml index 2974028..142aff3 100644 --- a/dev-example/kustomization.yaml +++ b/dev-example/kustomization.yaml @@ -20,6 +20,21 @@ patches: storage: 10Gi storageClassName: local-path +- patch: |- + kind: ConfigMap + apiVersion: v1 + metadata: + name: vm-operator + data: + # Keep in sync with config.yaml + config.yaml: | + "/Manager": + "/Controller": + namespace: vmop-dev + "/Reconciler": + runnerData: + storageClassName: null + - target: group: apps version: v1 diff --git a/dev-example/test-vm.yaml b/dev-example/test-vm.yaml index 500efd2..f82c973 100644 --- a/dev-example/test-vm.yaml +++ b/dev-example/test-vm.yaml @@ -14,11 +14,6 @@ spec: cpu: 1 memory: 2Gi - loadBalancerService: - labels: - test2: null - test3: added - vm: # state: Running bootMenu: yes diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index f0d47ec..0d32058 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -21,6 +21,8 @@ dependencies { runtimeOnly 'com.electronwill.night-config:yaml:3.6.6' runtimeOnly 'org.slf4j:slf4j-jdk14:[2.0.7,3)' + + testImplementation 'io.fabric8:kubernetes-client:6.8.1' } application { @@ -75,3 +77,18 @@ task pushImages { dependsOn pushLatestImage } +test { + enabled = project.hasProperty("k8s.testCluster") + + dependsOn project.tasks["pushImages"] + + useJUnitPlatform() + + testLogging { + showStandardStreams = true + } + + systemProperty "k8s.testCluster", project.hasProperty("k8s.testCluster") + ? project.getProperty("k8s.testCluster") : null +} + diff --git a/org.jdrupes.vmoperator.manager/test-resources/unittest-vm.yaml b/org.jdrupes.vmoperator.manager/test-resources/unittest-vm.yaml new file mode 100644 index 0000000..0d395bd --- /dev/null +++ b/org.jdrupes.vmoperator.manager/test-resources/unittest-vm.yaml @@ -0,0 +1,35 @@ +apiVersion: "vmoperator.jdrupes.org/v1" +kind: VirtualMachine +metadata: + namespace: vmop-dev + name: unittest-vm +spec: + resources: + requests: + cpu: 1 + memory: 2Gi + + loadBalancerService: + labels: + test2: null + test3: added + + vm: + # state: Running + maximumRam: 4Gi + currentRam: 2Gi + maximumCpus: 4 + currentCpus: 2 + powerdownTimeout: 1 + + networks: + - user: {} + disks: + - cdrom: + # image: "" + image: https://download.fedoraproject.org/pub/fedora/linux/releases/38/Workstation/x86_64/iso/Fedora-Workstation-Live-x86_64-38-1.6.iso + # image: "Fedora-Workstation-Live-x86_64-38-1.6.iso" + + display: + spice: + port: 5812 diff --git a/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java b/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java new file mode 100644 index 0000000..9452608 --- /dev/null +++ b/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java @@ -0,0 +1,81 @@ +package org.jdrupes.vmoperator.manager; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.kubernetes.client.dsl.base.ResourceDefinitionContext; +import io.fabric8.kubernetes.client.Config; + +class BasicTests { + + private static KubernetesClient client; + private static ResourceDefinitionContext vmsContext; + + @BeforeAll + static void setUpBeforeClass() throws Exception { + var testCluster = System.getProperty("k8s.testCluster"); + assertNotNull(testCluster); + + // Get client + client = new KubernetesClientBuilder() + .withConfig(Config.autoConfigure(testCluster)).build(); + + // Context for working with our CR + vmsContext = new ResourceDefinitionContext.Builder() + .withGroup("vmoperator.jdrupes.org").withKind("VirtualMachine") + .withPlural("vms").withNamespaced(true).withVersion("v1").build(); + + // Cleanup + var resourcesInNamespace = client.genericKubernetesResources(vmsContext) + .inNamespace("vmop-dev"); + resourcesInNamespace.withName("unittest-vm").delete(); + + // Update pod by scaling deployment + client.apps().deployments().inNamespace("vmop-dev") + .withName("vm-operator").scale(0); + client.apps().deployments().inNamespace("vmop-dev") + .withName("vm-operator").scale(1); + + // Wait until available + for (int i = 0; i < 10; i++) { + if (client.apps().deployments().inNamespace("vmop-dev") + .withName("vm-operator").get().getStatus().getConditions() + .stream().filter(c -> "Available".equals(c.getType())).findAny() + .isPresent()) { + return; + } + } + fail("vm-operator not deployed."); + } + + @AfterAll + static void tearDownAfterClass() throws Exception { + // Bring down manager + client.apps().deployments().inNamespace("vmop-dev") + .withName("vm-operator").scale(0); + client.close(); + } + + @Test + void test() throws IOException { + // Load from Yaml + var vm = client.genericKubernetesResources(vmsContext) + .load(Files + .newInputStream(Path.of("test-resources/unittest-vm.yaml"))); + // Create Custom Resource + vm.create(); + + // Cleanup + var resourcesInNamespace = client.genericKubernetesResources(vmsContext) + .inNamespace("vmop-dev"); + resourcesInNamespace.withName("unittest-vm").delete(); + } + +} diff --git a/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/package-info.java b/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/package-info.java new file mode 100644 index 0000000..7a983a6 --- /dev/null +++ b/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/package-info.java @@ -0,0 +1 @@ +package org.jdrupes.vmoperator.manager; \ No newline at end of file diff --git a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs index c3c017d..c3d4bc7 100644 --- a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs +++ b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs @@ -1,5 +1,5 @@ # -#Wed Aug 02 12:50:19 CEST 2023 +#Sat Aug 19 11:33:46 CEST 2023 org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert @@ -22,12 +22,12 @@ org.eclipse.jdt.core.formatter.blank_lines_after_package=1 org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.comment.indent_root_tags=false org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=false org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert @@ -43,8 +43,8 @@ org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invoc org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=true org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=true org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 @@ -63,8 +63,8 @@ org.eclipse.jdt.core.formatter.alignment_for_type_parameters=16 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert From 9b46cfa8bba0f4bbf83f6ef00cbf598e6f02bf05 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 19 Aug 2023 15:29:59 +0200 Subject: [PATCH 014/462] Get CI running again. --- org.jdrupes.vmoperator.manager/build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index 0d32058..d55cd05 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -80,7 +80,9 @@ task pushImages { test { enabled = project.hasProperty("k8s.testCluster") - dependsOn project.tasks["pushImages"] + if (enabled) { + dependsOn project.tasks["pushImages"] + } useJUnitPlatform() From cc15803ad67045c836833c2134d3fe2de4c1d875 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 19 Aug 2023 15:46:03 +0200 Subject: [PATCH 015/462] Add real test. --- .../vmoperator/manager/BasicTests.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java b/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java index 9452608..780a1fa 100644 --- a/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java +++ b/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java @@ -51,6 +51,7 @@ class BasicTests { .isPresent()) { return; } + Thread.sleep(1000); } fail("vm-operator not deployed."); } @@ -64,7 +65,7 @@ class BasicTests { } @Test - void test() throws IOException { + void test() throws IOException, InterruptedException { // Load from Yaml var vm = client.genericKubernetesResources(vmsContext) .load(Files @@ -72,10 +73,36 @@ class BasicTests { // Create Custom Resource vm.create(); + // Wait for created resources + assertTrue(waitForConfigMap()); + assertTrue(waitForStatefulSet()); + // Cleanup var resourcesInNamespace = client.genericKubernetesResources(vmsContext) .inNamespace("vmop-dev"); resourcesInNamespace.withName("unittest-vm").delete(); } + private boolean waitForConfigMap() throws InterruptedException { + for (int i = 0; i < 10; i++) { + if (client.configMaps().inNamespace("vmop-dev") + .withName("vm-operator").get() != null) { + return true; + } + Thread.sleep(1000); + } + return false; + } + + private boolean waitForStatefulSet() throws InterruptedException { + for (int i = 0; i < 10; i++) { + if (client.apps().statefulSets().inNamespace("vmop-dev") + .withName("unittest-vm").get() != null) { + return true; + } + Thread.sleep(1000); + } + return false; + } + } From 15d7589144daf005103660c33ae77293771d7450 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 19 Aug 2023 17:56:10 +0200 Subject: [PATCH 016/462] Pretty print size. --- .../vmoperator/manager/runnerConfig.ftl.yaml | 8 ++--- .../vmoperator/manager/Reconciler.java | 33 +++++++++++++++++++ .../vmoperator/runner/qemu/Configuration.java | 6 ++-- .../{ParseUtils.java => Convertions.java} | 24 +++++++++++++- 4 files changed, 63 insertions(+), 8 deletions(-) rename org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/{ParseUtils.java => Convertions.java} (79%) diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml index 7aaae51..dce5148 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml @@ -85,7 +85,7 @@ data: # cause the corresponding property to be omitted from the "-smp" option. # If currentCpus is greater than maximumCpus, the latter is adjusted. <#if cr.spec.vm.maximumCpus?? > - maximumCpus: ${ cr.spec.vm.maximumCpus.asInt?c } + maximumCpus: ${ parseQuantity(cr.spec.vm.maximumCpus.asString)?c } <#if cr.spec.vm.cpuTopology?? > cpuSockets: ${ cr.spec.vm.cpuTopology.cpuSockets.asInt?c } @@ -94,14 +94,14 @@ data: threadsPerCore: ${ cr.spec.vm.cpuTopology.threadsPerCore.asInt?c } <#if cr.spec.vm.currentCpus?? > - currentCpus: ${ cr.spec.vm.currentCpus.asInt?c } + currentCpus: ${ parseQuantity(cr.spec.vm.currentCpus.asString)?c } # RAM settings # Maximum defaults to 1G - maximumRam: "${ cr.spec.vm.maximumRam.asString }" + maximumRam: "${ formatMemory(parseQuantity(cr.spec.vm.maximumRam.asString)) }" <#if cr.spec.vm.currentRam?? > - currentRam: "${ cr.spec.vm.currentRam.asString }" + currentRam: "${ formatMemory(parseQuantity(cr.spec.vm.currentRam.asString)) }" # RTC settings. diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index f135a7e..7815864 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -24,6 +24,7 @@ import freemarker.core.ParseException; import freemarker.template.Configuration; import freemarker.template.DefaultObjectWrapperBuilder; import freemarker.template.MalformedTemplateNameException; +import freemarker.template.SimpleNumber; import freemarker.template.TemplateException; import freemarker.template.TemplateExceptionHandler; import freemarker.template.TemplateHashModel; @@ -34,6 +35,8 @@ import io.kubernetes.client.custom.Quantity; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; @@ -42,6 +45,7 @@ import java.util.Map; import java.util.Optional; import static org.jdrupes.vmoperator.manager.Constants.VM_OP_GROUP; import org.jdrupes.vmoperator.manager.VmDefChanged.Type; +import org.jdrupes.vmoperator.util.Convertions; import org.jdrupes.vmoperator.util.ExtendedObjectWrapper; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; @@ -203,6 +207,7 @@ public class Reconciler extends Component { lbReconciler.reconcile(event, model, channel); } + @SuppressWarnings("PMD.CognitiveComplexity") private Map prepareModel(JsonObject vmDef) throws TemplateModelException { @SuppressWarnings("PMD.UseConcurrentHashMap") @@ -236,6 +241,34 @@ public class Reconciler extends Component { } } }); + model.put("formatMemory", new TemplateMethodModelEx() { + @Override + @SuppressWarnings("PMD.PreserveStackTrace") + public Object exec(@SuppressWarnings("rawtypes") List arguments) + throws TemplateModelException { + var arg = arguments.get(0); + if (arg instanceof SimpleNumber number) { + arg = number.getAsNumber(); + } + BigInteger bigInt; + if (arg instanceof BigInteger value) { + bigInt = value; + } else if (arg instanceof BigDecimal dec) { + try { + bigInt = dec.toBigIntegerExact(); + } catch (ArithmeticException e) { + return arg; + } + } else if (arg instanceof Integer value) { + bigInt = BigInteger.valueOf(value); + } else if (arg instanceof Long value) { + bigInt = BigInteger.valueOf(value); + } else { + return arg; + } + return Convertions.formatMemory(bigInt); + } + }); return model; } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java index 0ecde1f..c83b021 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java @@ -30,7 +30,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.jdrupes.vmoperator.util.Dto; import org.jdrupes.vmoperator.util.FsdUtils; -import org.jdrupes.vmoperator.util.ParseUtils; +import org.jdrupes.vmoperator.util.Convertions; /** * The configuration information from the configuration file. @@ -144,7 +144,7 @@ public class Configuration implements Dto { * @param value the new maximum ram */ public void setMaximumRam(String value) { - maximumRam = ParseUtils.parseMemory(value); + maximumRam = Convertions.parseMemory(value); } /** @@ -153,7 +153,7 @@ public class Configuration implements Dto { * @param value the new current ram */ public void setCurrentRam(String value) { - currentRam = ParseUtils.parseMemory(value); + currentRam = Convertions.parseMemory(value); } } diff --git a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/ParseUtils.java b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Convertions.java similarity index 79% rename from org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/ParseUtils.java rename to org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Convertions.java index 8226c18..d2975b4 100644 --- a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/ParseUtils.java +++ b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Convertions.java @@ -29,11 +29,14 @@ import java.util.regex.Pattern; * Provides methods for parsing "official" memory sizes.. */ @SuppressWarnings("PMD.UseUtilityClass") -public class ParseUtils { +public class Convertions { @SuppressWarnings({ "PMD.UseConcurrentHashMap", "PMD.FieldNamingConventions", "PMD.VariableNamingConventions" }) private static final Map unitMap = new HashMap<>(); + @SuppressWarnings({ "PMD.FieldNamingConventions", + "PMD.VariableNamingConventions" }) + private static final List> unitMappings; @SuppressWarnings({ "PMD.FieldNamingConventions", "PMD.VariableNamingConventions" }) private static final Pattern memorySize @@ -55,6 +58,9 @@ public class ParseUtils { unitMap.put(unit, factor); factor = factor.multiply(scale); } + unitMappings = unitMap.entrySet().stream() + .sorted((a, b) -> -1 * a.getValue().compareTo(b.getValue())) + .toList(); } /** @@ -91,4 +97,20 @@ public class ParseUtils { .toBigInteger(); } + /** + * Format memory size for humans. + * + * @param size the size + * @return the string + */ + public static String formatMemory(BigInteger size) { + for (var mapping : unitMappings) { + if (size.compareTo(mapping.getValue()) >= 0 + && size.mod(mapping.getValue()).equals(BigInteger.ZERO)) { + return size.divide(mapping.getValue()).toString() + + " " + mapping.getKey(); + } + } + return size.toString(); + } } From 9706a9af8c3a90c6b67c4bd0fe7ebac783822844 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 19 Aug 2023 18:00:38 +0200 Subject: [PATCH 017/462] Fix order. --- .../src/org/jdrupes/vmoperator/runner/qemu/Configuration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java index c83b021..51ed47e 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java @@ -28,9 +28,9 @@ import java.util.Set; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; +import org.jdrupes.vmoperator.util.Convertions; import org.jdrupes.vmoperator.util.Dto; import org.jdrupes.vmoperator.util.FsdUtils; -import org.jdrupes.vmoperator.util.Convertions; /** * The configuration information from the configuration file. From 6b6e48650d159d203855e1c810bd4c95d662a924 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 19 Aug 2023 18:05:18 +0200 Subject: [PATCH 018/462] Avoid trailing space. --- .../src/org/jdrupes/vmoperator/util/Convertions.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Convertions.java b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Convertions.java index d2975b4..2dc46e5 100644 --- a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Convertions.java +++ b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Convertions.java @@ -107,8 +107,8 @@ public class Convertions { for (var mapping : unitMappings) { if (size.compareTo(mapping.getValue()) >= 0 && size.mod(mapping.getValue()).equals(BigInteger.ZERO)) { - return size.divide(mapping.getValue()).toString() - + " " + mapping.getKey(); + return (size.divide(mapping.getValue()).toString() + + " " + mapping.getKey()).trim(); } } return size.toString(); From aab15d8b6fe44cd4bd3af604ed6a409a42f40914 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 19 Aug 2023 18:29:47 +0200 Subject: [PATCH 019/462] Test size setting. --- .../jdrupes/vmoperator/manager/BasicTests.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java b/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java index 780a1fa..532a25a 100644 --- a/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java +++ b/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java @@ -5,9 +5,13 @@ import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Map; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; import io.fabric8.kubernetes.client.dsl.base.ResourceDefinitionContext; @@ -77,6 +81,16 @@ class BasicTests { assertTrue(waitForConfigMap()); assertTrue(waitForStatefulSet()); + // Check config map + var config = client.configMaps().inNamespace("vmop-dev") + .withName("unittest-vm").get(); + var yaml = new Yaml(new SafeConstructor(new LoaderOptions())) + .load((String) config.getData().get("config.yaml")); + @SuppressWarnings("unchecked") + var currentRam = ((Map>>) yaml) + .get("/Runner").get("vm").get("maximumRam"); + assertEquals("4 GiB", currentRam); + // Cleanup var resourcesInNamespace = client.genericKubernetesResources(vmsContext) .inNamespace("vmop-dev"); @@ -86,7 +100,7 @@ class BasicTests { private boolean waitForConfigMap() throws InterruptedException { for (int i = 0; i < 10; i++) { if (client.configMaps().inNamespace("vmop-dev") - .withName("vm-operator").get() != null) { + .withName("unittest-vm").get() != null) { return true; } Thread.sleep(1000); From c69ae443959f2bef2c3de7a0672d05b33fef83d3 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 20 Aug 2023 19:11:38 +0200 Subject: [PATCH 020/462] Disable direct access to flash. --- .../templates/Standard-VM-latest.ftl.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml b/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml index 514f561..f8c3716 100644 --- a/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml +++ b/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml @@ -52,12 +52,14 @@ - [ "-global", "ICH9-LPC.disable_s3=1" ] - [ "-global", "ICH9-LPC.disable_s4=1" ] <#if firmwareRom??> - # * Provide ROM/EEPROM devices (instead of built-in BIOS) - - [ "-blockdev", "node-name=fw-rom-file,driver=file,cache.direct=on,\ + # * Provide ROM/EEPROM devices (instead of built-in BIOS). + # Don't use cache.direct=on for these as this can results in + # incredibly bad performance when booting. + - [ "-blockdev", "node-name=fw-rom-file,driver=file,\ filename=${ firmwareRom },auto-read-only=true,discard=unmap" ] - [ "-blockdev", "node-name=fw-rom-device,driver=raw,\ read-only=true,file=fw-rom-file" ] - - [ "-blockdev", "node-name=fw-eeprom-file,driver=file,cache.direct=on,\ + - [ "-blockdev", "node-name=fw-eeprom-file,driver=file,\ filename=${ firmwareVars },auto-read-only=true,discard=unmap" ] - [ "-blockdev", "node-name=fw-eeprom-device,driver=raw,\ read-only=false,file=fw-eeprom-file" ] From 9ed16f4c3aee3be80bef6fe3bb23dd982acaf62f Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 20 Aug 2023 19:18:36 +0200 Subject: [PATCH 021/462] Add some debug logging. --- .../src/org/jdrupes/vmoperator/runner/qemu/Runner.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index 25a5872..675106e 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -311,6 +311,9 @@ public class Runner extends Component { .ofNullable(config.template).orElse(DEFAULT_TEMPLATE)); Files.deleteIfExists(templatePath); Files.copy(sourcePath, templatePath); + logger.fine(() -> "Using template " + sourcePath); + } else { + logger.fine(() -> "Using saved template."); } // Configure data model From a3bbeafdf6c2b46fb21b260d7979560b6cd287e5 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 20 Aug 2023 20:49:13 +0200 Subject: [PATCH 022/462] Provide default for runnerTemplate to make default for update effective. --- deploy/crds/vms-crd.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index b40dc8c..0b24bd6 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -924,6 +924,7 @@ spec: description: >- Runner template configuration. type: object + default: {} properties: source: type: string From 0d5e680318d4d1d3a40c6130e27712ded718d1b4 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 21 Aug 2023 10:06:57 +0200 Subject: [PATCH 023/462] Filter duplicate events. --- .../jdrupes/vmoperator/manager/VmChannel.java | 27 +++++++++++++++++++ .../jdrupes/vmoperator/manager/VmWatcher.java | 9 +++++++ 2 files changed, 36 insertions(+) diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmChannel.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmChannel.java index 4336ca8..47917e6 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmChannel.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmChannel.java @@ -27,11 +27,13 @@ import org.jgrapes.core.Subchannel.DefaultSubchannel; /** * A subchannel used to send the events related to a specific VM. */ +@SuppressWarnings("PMD.DataClass") public class VmChannel extends DefaultSubchannel { private final EventPipeline pipeline; private final ApiClient client; private JsonObject vmDefinition; + private long generation = -1; /** * Instantiates a new watch channel. @@ -68,6 +70,31 @@ public class VmChannel extends DefaultSubchannel { return vmDefinition; } + /** + * Gets the last processed generation. Returns -1 if no + * definition has been processed yet. + * + * @return the generation + */ + public long generation() { + return generation; + } + + /** + * Sets the last processed generation. + * + * @param generation the generation to set + * @return true if value has changed + */ + @SuppressWarnings("PMD.LinguisticNaming") + public boolean setGeneration(long generation) { + if (this.generation == generation) { + return false; + } + this.generation = generation; + return true; + } + /** * Returns the pipeline. * diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java index f20c52e..61c23f0 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java @@ -244,6 +244,15 @@ public class VmWatcher extends Component { return null; } }); + if (channel == null) { + return; + } + + // Filter duplicates + if (!"DELETED".equals(item.type) && !channel + .setGeneration(item.object.getMetadata().getGeneration())) { + return; + } channel.pipeline().fire(new VmDefChanged(VmDefChanged.Type .valueOf(item.type), vmsCrd, item.object), channel); } From b919eb17ac7a80d246f4a0d9d488eb3540fcff4c Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 21 Aug 2023 16:55:14 +0200 Subject: [PATCH 024/462] Additional fail safe. --- .../jdrupes/vmoperator/manager/VmWatcher.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java index 61c23f0..f32c178 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java @@ -35,6 +35,8 @@ import io.kubernetes.client.util.generic.options.ListOptions; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -197,12 +199,15 @@ public class VmWatcher extends Component { return result; } + @SuppressWarnings("PMD.CognitiveComplexity") private void watchVmDefs(V1APIResource crd, String version) { - @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops", + "PMD.AvoidLiteralsInIfCondition", "PMD.AvoidCatchingThrowable" }) var watcher = new Thread(() -> { try { // Watch sometimes terminates without apparent reason. while (true) { + Instant started = Instant.now(); var client = Config.defaultClient(); var coa = new CustomObjectsApi(client); var call = coa.listNamespacedCustomObjectCall(VM_OP_GROUP, @@ -215,9 +220,20 @@ public class VmWatcher extends Component { for (Watch.Response item : watch) { handleVmDefinitionChange(crd, item); } - } catch (IllegalStateException e) { + } catch (Throwable e) { logger.log(Level.FINE, e, () -> "Probem watching " + "(retrying): " + e.getMessage()); + var runningFor = Duration + .between(started, Instant.now()).getSeconds(); + if (runningFor < 5) { + logger.log(Level.FINE, e, () -> "Waiting... "); + try { + Thread.sleep(5000 - runningFor * 1000); + } catch (InterruptedException e1) { + continue; + } + logger.log(Level.FINE, e, () -> "Restarting"); + } } } } catch (IOException | ApiException e) { From 79da75e806ec9cfe2e3df69088d874db73c985c3 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 21 Aug 2023 16:55:25 +0200 Subject: [PATCH 025/462] Test before push. --- org.jdrupes.vmoperator.manager/build.gradle | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index d55cd05..6581051 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -72,16 +72,21 @@ task pushLatestImage(type: Exec) { + "/${project.name}:latest" } -task pushImages { +task pushForTest { dependsOn pushImage dependsOn pushLatestImage } +task pushImages { + dependsOn test + dependsOn pushForTest +} + test { enabled = project.hasProperty("k8s.testCluster") if (enabled) { - dependsOn project.tasks["pushImages"] + dependsOn project.tasks["pushForTest"] } useJUnitPlatform() From ef708476ca44e44eccf6c3540ae3ef038fb689ad Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 21 Aug 2023 17:40:51 +0200 Subject: [PATCH 026/462] Be a bit more specific with exception handling. --- .../jdrupes/vmoperator/manager/VmWatcher.java | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java index f32c178..a304c0b 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java @@ -199,15 +199,14 @@ public class VmWatcher extends Component { return result; } - @SuppressWarnings("PMD.CognitiveComplexity") private void watchVmDefs(V1APIResource crd, String version) { @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops", - "PMD.AvoidLiteralsInIfCondition", "PMD.AvoidCatchingThrowable" }) + "PMD.AvoidCatchingThrowable", "PMD.AvoidCatchingGenericException" }) var watcher = new Thread(() -> { try { // Watch sometimes terminates without apparent reason. while (true) { - Instant started = Instant.now(); + Instant startedAt = Instant.now(); var client = Config.defaultClient(); var coa = new CustomObjectsApi(client); var call = coa.listNamespacedCustomObjectCall(VM_OP_GROUP, @@ -220,23 +219,14 @@ public class VmWatcher extends Component { for (Watch.Response item : watch) { handleVmDefinitionChange(crd, item); } - } catch (Throwable e) { - logger.log(Level.FINE, e, () -> "Probem watching " - + "(retrying): " + e.getMessage()); - var runningFor = Duration - .between(started, Instant.now()).getSeconds(); - if (runningFor < 5) { - logger.log(Level.FINE, e, () -> "Waiting... "); - try { - Thread.sleep(5000 - runningFor * 1000); - } catch (InterruptedException e1) { - continue; - } - logger.log(Level.FINE, e, () -> "Restarting"); - } + } catch (IOException | ApiException | RuntimeException e) { + logger.log(Level.FINE, e, () -> "Problem watching \"" + + crd.getName() + "\" (will retry): " + + e.getMessage()); + delayRestart(startedAt); } } - } catch (IOException | ApiException e) { + } catch (Throwable e) { logger.log(Level.SEVERE, e, () -> "Probem watching: " + e.getMessage()); } @@ -246,6 +236,21 @@ public class VmWatcher extends Component { watcher.start(); } + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") + private void delayRestart(Instant started) { + var runningFor = Duration + .between(started, Instant.now()).toMillis(); + if (runningFor < 5000) { + logger.log(Level.FINE, () -> "Waiting... "); + try { + Thread.sleep(5000 - runningFor); + } catch (InterruptedException e1) { // NOPMD + // Retry + } + logger.log(Level.FINE, () -> "Retrying"); + } + } + private void handleVmDefinitionChange(V1APIResource vmsCrd, Watch.Response item) { V1ObjectMeta metadata = item.object.getMetadata(); From 66f8d15dc0c8b4102b5e0bdf3f3eb3436267b3d7 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 22 Aug 2023 14:44:13 +0200 Subject: [PATCH 027/462] Remove deprecated (and redundant) property. --- deploy/vmop-deployment.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/deploy/vmop-deployment.yaml b/deploy/vmop-deployment.yaml index f870ead..04fecde 100644 --- a/deploy/vmop-deployment.yaml +++ b/deploy/vmop-deployment.yaml @@ -47,7 +47,6 @@ spec: nodeSelector: kubernetes.io/os: linux serviceAccountName: vm-operator - serviceAccount: vm-operator securityContext: runAsUser: 65534 runAsNonRoot: true From 9af84091b5bc082bcaaaf220f2025300da3525cc Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 22 Aug 2023 18:38:55 +0200 Subject: [PATCH 028/462] fix erroneous usage of qemu-base. --- .../src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch index b81cace..01fb4bb 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch @@ -3,7 +3,7 @@ FROM archlinux/archlinux RUN systemd-firstboot --setup-machine-id RUN pacman -Suy --noconfirm \ - && pacman -S --noconfirm which qemu-base virtiofsd \ + && pacman -S --noconfirm which qemu-full virtiofsd \ edk2-ovmf swtpm iproute2 bridge-utils jre17-openjdk-headless \ && pacman -Scc --noconfirm From 9305ebe927c19dde83e3582d0829889bb5934549 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 22 Aug 2023 23:05:23 +0200 Subject: [PATCH 029/462] Add the paths used by alpine. --- .../resources/org/jdrupes/vmoperator/runner/qemu/defaults.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/org.jdrupes.vmoperator.runner.qemu/resources/org/jdrupes/vmoperator/runner/qemu/defaults.yaml b/org.jdrupes.vmoperator.runner.qemu/resources/org/jdrupes/vmoperator/runner/qemu/defaults.yaml index 70de565..9aadbf6 100644 --- a/org.jdrupes.vmoperator.runner.qemu/resources/org/jdrupes/vmoperator/runner/qemu/defaults.yaml +++ b/org.jdrupes.vmoperator.runner.qemu/resources/org/jdrupes/vmoperator/runner/qemu/defaults.yaml @@ -7,9 +7,11 @@ "rom": - "/usr/share/edk2/ovmf/OVMF_CODE.fd" - "/usr/share/edk2/x64/OVMF_CODE.fd" + - "/usr/share/OVMF/OVMF_CODE.fd" "vars": - "/usr/share/edk2/ovmf/OVMF_VARS.fd" - "/usr/share/edk2/x64/OVMF_VARS.fd" + - "/usr/share/OVMF/OVMF_VARS.fd" "uefi-4m": "rom": - "/usr/share/edk2/ovmf-4m/OVMF_CODE.fd" From 2d35738f5a9d0395be4adced5dbcf3e823cc6e79 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Wed, 23 Aug 2023 10:29:58 +0200 Subject: [PATCH 030/462] Create and use alpine based image. Arch based is simply too big. --- deploy/crds/vms-crd.yaml | 2 +- .../build.gradle | 48 +++++++++++++++++-- .../runner/qemu/Containerfile.alpine | 12 +++++ 3 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index 0b24bd6..3e33d0b 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -26,7 +26,7 @@ spec: - required: - repository - path - default: { source: "ghcr.io/mnlipp/org.jdrupes.vmoperator.runner.qemu-arch:latest" } + default: { source: "ghcr.io/mnlipp/org.jdrupes.vmoperator.runner.qemu-alpine:latest" } properties: source: description: >- diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle index ff9a257..e0fb8ca 100644 --- a/org.jdrupes.vmoperator.runner.qemu/build.gradle +++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle @@ -36,14 +36,14 @@ task buildArchImage(type: Exec) { dependsOn installDist inputs.files 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch' - commandLine 'podman', 'build', '-t', "${project.name}:${project.version}",\ + commandLine 'podman', 'build', '-t', "${project.name}-arch:${project.version}",\ '-f', 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch', '.' } task tagLatestArchImage(type: Exec) { dependsOn buildArchImage - commandLine 'podman', 'tag', "${project.name}:${project.version}",\ + commandLine 'podman', 'tag', "${project.name}-arch:${project.version}",\ "${project.name}:latest" } @@ -56,7 +56,7 @@ task pushArchImage(type: Exec) { dependsOn buildArchImage commandLine 'podman', 'push', '--tls-verify=false', \ - "localhost/${project.name}:${project.version}", \ + "localhost/${project.name}-arch:${project.version}", \ "${project.rootProject.properties['docker.registry']}" \ + "/${project.name}-arch:${project.version}" } @@ -65,13 +65,53 @@ task pushArchLatestImage(type: Exec) { dependsOn buildLatestArchImage commandLine 'podman', 'push', '--tls-verify=false', \ - "localhost/${project.name}:${project.version}", \ + "localhost/${project.name}-arch:${project.version}", \ "${project.rootProject.properties['docker.registry']}" \ + "/${project.name}-arch:latest" } +task buildAlpineImage(type: Exec) { + dependsOn installDist + inputs.files 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine' + + commandLine 'podman', 'build', '-t', "${project.name}-alpine:${project.version}",\ + '-f', 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine', '.' +} + +task tagLatestAlpineImage(type: Exec) { + dependsOn buildAlpineImage + + commandLine 'podman', 'tag', "${project.name}-alpine:${project.version}",\ + "${project.name}-alpine:latest" +} + +task buildLatestAlpineImage { + dependsOn buildAlpineImage + dependsOn tagLatestAlpineImage +} + +task pushAlpineImage(type: Exec) { + dependsOn buildAlpineImage + + commandLine 'podman', 'push', '--tls-verify=false', \ + "localhost/${project.name}-alpine:${project.version}", \ + "${project.rootProject.properties['docker.registry']}" \ + + "/${project.name}-alpine:${project.version}" +} + +task pushAlpineLatestImage(type: Exec) { + dependsOn buildLatestAlpineImage + + commandLine 'podman', 'push', '--tls-verify=false', \ + "localhost/${project.name}-alpine:${project.version}", \ + "${project.rootProject.properties['docker.registry']}" \ + + "/${project.name}-alpine:latest" +} + task pushImages { dependsOn pushArchImage dependsOn pushArchLatestImage + dependsOn pushAlpineImage + dependsOn pushAlpineLatestImage } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine new file mode 100644 index 0000000..def82ef --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine @@ -0,0 +1,12 @@ +FROM docker.io/alpine + +RUN apk update + +RUN apk add qemu-system-x86_64 qemu-modules ovmf swtpm openjdk17 + +RUN mkdir -p /etc/qemu && echo "allow all" > /etc/qemu/bridge.conf + +COPY build/install/vm-runner.qemu /opt/vmrunner +COPY templates/* /opt/vmrunner/templates/ + +CMD ["/opt/vmrunner/bin/vm-runner.qemu"] From ce1870d16d24b214c0417f7664b1b7a3679d59de Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Wed, 23 Aug 2023 10:56:41 +0200 Subject: [PATCH 031/462] Adjust requested memory. --- deploy/vmop-deployment.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/vmop-deployment.yaml b/deploy/vmop-deployment.yaml index 04fecde..648cc39 100644 --- a/deploy/vmop-deployment.yaml +++ b/deploy/vmop-deployment.yaml @@ -36,7 +36,7 @@ spec: resources: requests: cpu: 100m - memory: 32Mi + memory: 128Mi volumes: - name: config configMap: From 219d8aa91a29766b61e4667676649e7d6241edc1 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Wed, 23 Aug 2023 10:58:28 +0200 Subject: [PATCH 032/462] Use alpine for testing and also create (sw)tpm. --- dev-example/test-vm.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev-example/test-vm.yaml b/dev-example/test-vm.yaml index f82c973..0cd820b 100644 --- a/dev-example/test-vm.yaml +++ b/dev-example/test-vm.yaml @@ -6,7 +6,7 @@ metadata: spec: image: repository: docker-registry.lan.mnl.de - path: vmoperator/org.jdrupes.vmoperator.runner.qemu-arch + path: vmoperator/org.jdrupes.vmoperator.runner.qemu-alpine pullPolicy: Always resources: @@ -17,6 +17,7 @@ spec: vm: # state: Running bootMenu: yes + useTpm: true maximumRam: 8Gi currentRam: 4Gi maximumCpus: 4 From c5818b6bf47d8b11eaff201d1b1272ce34b55b75 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Wed, 23 Aug 2023 12:00:52 +0200 Subject: [PATCH 033/462] Improve error handling. --- .../vmoperator/runner/qemu/CpuController.java | 5 +++ .../runner/qemu/events/MonitorResult.java | 39 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java index 512db1b..6d8ccc1 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java @@ -85,6 +85,11 @@ public class CpuController extends Component { */ @Handler public void onHotpluggableCpuStatus(HotpluggableCpuStatus result) { + if (!result.successful()) { + logger.warning(() -> "Failed to get hotpluggable CPU status " + + "(won't adjust number of CPUs.): " + result.errorMessage()); + } + // Sort List used = new ArrayList<>(); List unused = new ArrayList<>(); diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java index ee8515f..83295dd 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java @@ -19,6 +19,7 @@ package org.jdrupes.vmoperator.runner.qemu.events; import com.fasterxml.jackson.databind.JsonNode; +import java.util.Optional; import org.jdrupes.vmoperator.runner.qemu.commands.QmpAddCpu; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand; import org.jdrupes.vmoperator.runner.qemu.commands.QmpDelCpu; @@ -89,6 +90,7 @@ public class MonitorResult extends Event { * * @return the json node */ + @SuppressWarnings("PMD.AvoidDuplicateLiterals") public JsonNode values() { if (response.has("return")) { return response.get("return"); @@ -99,6 +101,43 @@ public class MonitorResult extends Event { return null; } + /** + * Returns the error class if this result is an error. + * + * @return the optional + */ + public Optional errorClass() { + if (!response.has("error")) { + return Optional.empty(); + } + return Optional.ofNullable(response.get("error").get("class").asText()); + } + + /** + * Returns the error description if this result is an error. + * + * @return the optional + */ + public Optional errorDescription() { + if (!response.has("error")) { + return Optional.empty(); + } + return Optional.ofNullable(response.get("error").get("desc").asText()); + } + + /** + * Combines error class and error description to a string + * "class: desc". Returns an empty string is this result is not an error. + * + * @return the string + */ + public String errorMessage() { + if (successful()) { + return ""; + } + return errorClass().get() + ": " + errorDescription().get(); + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); From 7e094e720b7b59a5e50f4a9a4ad29a6000ec76e6 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Wed, 23 Aug 2023 13:30:28 +0200 Subject: [PATCH 034/462] Restore arch image as default because of https://github.com/mnlipp/VM-Operator/issues/1 --- deploy/crds/vms-crd.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index 3e33d0b..0b24bd6 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -26,7 +26,7 @@ spec: - required: - repository - path - default: { source: "ghcr.io/mnlipp/org.jdrupes.vmoperator.runner.qemu-alpine:latest" } + default: { source: "ghcr.io/mnlipp/org.jdrupes.vmoperator.runner.qemu-arch:latest" } properties: source: description: >- From 70ecc3946611bc6146fb58fab78a62a5288b1df7 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 24 Aug 2023 13:24:32 +0200 Subject: [PATCH 035/462] Don't round, CPU fractions are supported. --- .../resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml index 053e1c3..60834b9 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml @@ -99,7 +99,7 @@ spec: <#if reconciler.cpuOvercommit??> <#assign factor = reconciler.cpuOvercommit * 1.0 /> - cpu: ${ (parseQuantity(cr.spec.vm.currentCpus.asString) / factor)?floor?c } + cpu: ${ (parseQuantity(cr.spec.vm.currentCpus.asString) / factor)?c } <#if cr.spec.vm.currentRam?? > <#assign factor = 1.25 /> From c13344d0e2aa1dc65b4f0ec82ea3cf038cb40960 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 24 Aug 2023 13:25:25 +0200 Subject: [PATCH 036/462] Force parallel GC. --- org.jdrupes.vmoperator.runner.qemu/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle index e0fb8ca..a3fc61a 100644 --- a/org.jdrupes.vmoperator.runner.qemu/build.gradle +++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle @@ -25,7 +25,7 @@ dependencies { application { applicationName = 'vm-runner.qemu' // Keep sync'd with deploy/vmop-deployment.yaml - applicationDefaultJvmArgs = ['-Xmx32m', + applicationDefaultJvmArgs = ['-Xmx32m', '-XX:+UseParallelGC', '-Djava.util.logging.manager=org.jdrupes.vmoperator.util.LongLoggingManager' ] // Define the main class for the application. From 5fe71670e21d54d55cf893002f78569bc21d8d57 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 24 Aug 2023 13:56:23 +0200 Subject: [PATCH 037/462] Add useful startup information. --- .../src/org/jdrupes/vmoperator/manager/Manager.java | 6 +++++- .../src/org/jdrupes/vmoperator/runner/qemu/Runner.java | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java index fed9740..b67ab28 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java @@ -132,7 +132,11 @@ public class Manager extends Component { @SuppressWarnings("PMD.SignatureDeclareThrowsException") public static void main(String[] args) { try { - Logger.getLogger(Manager.class.getName()).fine(() -> "Version: " + var logger = Logger.getLogger(Manager.class.getName()); + logger.fine(() -> "Running on " + System.getProperty("java.vm.name") + + " (" + System.getProperty("java.vm.version") + ")" + + " from " + System.getProperty("java.vm.vendor")); + logger.fine(() -> "Version: " + Manager.class.getPackage().getImplementationVersion()); // Parse the command line arguments diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index 675106e..65aa88b 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -576,9 +576,12 @@ public class Runner extends Component { public static void main(String[] args) { // The Runner is the root component try { - Logger.getLogger(Runner.class.getName()) - .fine(() -> "Version: " - + Runner.class.getPackage().getImplementationVersion()); + var logger = Logger.getLogger(Runner.class.getName()); + logger.fine(() -> "Running on " + System.getProperty("java.vm.name") + + " (" + System.getProperty("java.vm.version") + ")" + + " from " + System.getProperty("java.vm.vendor")); + logger.fine(() -> "Version: " + + Runner.class.getPackage().getImplementationVersion()); CommandLineParser parser = new DefaultParser(); // parse the command line arguments final Options options = new Options(); From e10256f04eb3d1f4c04f6bcac13d736118a66c15 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 24 Aug 2023 13:56:57 +0200 Subject: [PATCH 038/462] There's no advantage in using graal. --- .../src/org/jdrupes/vmoperator/manager/Containerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Containerfile b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Containerfile index ce04b0f..c212945 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Containerfile +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Containerfile @@ -1,6 +1,4 @@ -FROM ghcr.io/graalvm/jdk-community:17 - -RUN microdnf install findutils && microdnf clean all +FROM docker.io/eclipse-temurin:17-jre-alpine COPY build/install/vm-manager /opt/vmmanager From 998e9c6aa1a2a9bed7a20efe4da0cc05093f127b Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 24 Aug 2023 13:57:23 +0200 Subject: [PATCH 039/462] Force parallel GC. --- org.jdrupes.vmoperator.manager/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index 6581051..10a122c 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -27,7 +27,7 @@ dependencies { application { applicationName = 'vm-manager' - applicationDefaultJvmArgs = ['-Xmx50m', + applicationDefaultJvmArgs = ['-Xmx50m', '-XX:+UseParallelGC', '-Djava.util.logging.manager=org.jdrupes.vmoperator.util.LongLoggingManager' ] // Define the main class for the application. From e75f12c69fe8df29c1a63cf677a3e9772834f3f9 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 24 Aug 2023 14:00:33 +0200 Subject: [PATCH 040/462] Prioritize messages. --- .../src/org/jdrupes/vmoperator/manager/Manager.java | 6 +++--- .../src/org/jdrupes/vmoperator/runner/qemu/Runner.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java index b67ab28..5b55ca8 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java @@ -133,11 +133,11 @@ public class Manager extends Component { public static void main(String[] args) { try { var logger = Logger.getLogger(Manager.class.getName()); - logger.fine(() -> "Running on " + System.getProperty("java.vm.name") - + " (" + System.getProperty("java.vm.version") + ")" - + " from " + System.getProperty("java.vm.vendor")); logger.fine(() -> "Version: " + Manager.class.getPackage().getImplementationVersion()); + logger.fine(() -> "running on " + System.getProperty("java.vm.name") + + " (" + System.getProperty("java.vm.version") + ")" + + " from " + System.getProperty("java.vm.vendor")); // Parse the command line arguments CommandLineParser parser = new DefaultParser(); diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index 65aa88b..1370e18 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -577,11 +577,11 @@ public class Runner extends Component { // The Runner is the root component try { var logger = Logger.getLogger(Runner.class.getName()); - logger.fine(() -> "Running on " + System.getProperty("java.vm.name") - + " (" + System.getProperty("java.vm.version") + ")" - + " from " + System.getProperty("java.vm.vendor")); logger.fine(() -> "Version: " + Runner.class.getPackage().getImplementationVersion()); + logger.fine(() -> "running on " + System.getProperty("java.vm.name") + + " (" + System.getProperty("java.vm.version") + ")" + + " from " + System.getProperty("java.vm.vendor")); CommandLineParser parser = new DefaultParser(); // parse the command line arguments final Options options = new Options(); From 6840db635e13b5c565ffcfee23a3ba5bc89d3991 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 1 Sep 2023 22:22:57 +0200 Subject: [PATCH 041/462] Upgrade libraries. --- org.jdrupes.vmoperator.manager/build.gradle | 6 +++--- org.jdrupes.vmoperator.runner.qemu/build.gradle | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index 10a122c..2c2dd7f 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -12,17 +12,17 @@ dependencies { implementation 'org.jgrapes:org.jgrapes.core:[1.19.0,2)' implementation 'org.jgrapes:org.jgrapes.io:[2.7.0,3)' implementation 'org.jgrapes:org.jgrapes.http:[3.1.0,4)' - implementation 'org.jgrapes:org.jgrapes.util:[1.29.0,2)' + implementation 'org.jgrapes:org.jgrapes.util:[1.31.0,2)' implementation project(':org.jdrupes.vmoperator.util') implementation 'commons-cli:commons-cli:1.5.0' implementation 'org.freemarker:freemarker:[2.3.32,2.4)' implementation 'io.kubernetes:client-java:[18.0.0,19)' - runtimeOnly 'com.electronwill.night-config:yaml:3.6.6' + runtimeOnly 'com.electronwill.night-config:yaml:[3.6.7,3.7)' runtimeOnly 'org.slf4j:slf4j-jdk14:[2.0.7,3)' - testImplementation 'io.fabric8:kubernetes-client:6.8.1' + testImplementation 'io.fabric8:kubernetes-client:[6.8.1,6.9)' } application { diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle index a3fc61a..9f48a58 100644 --- a/org.jdrupes.vmoperator.runner.qemu/build.gradle +++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle @@ -12,14 +12,12 @@ dependencies { implementation 'org.jgrapes:org.jgrapes.core:[1.19.0,2)' implementation 'org.jgrapes:org.jgrapes.io:[2.7.0,3)' implementation 'org.jgrapes:org.jgrapes.http:[3.1.0,4)' - implementation 'org.jgrapes:org.jgrapes.util:[1.29.0,2)' + implementation 'org.jgrapes:org.jgrapes.util:[1.31.0,2)' implementation project(':org.jdrupes.vmoperator.util') implementation 'commons-cli:commons-cli:1.5.0' implementation 'org.freemarker:freemarker:[2.3.32,2.4)' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:[2.15.1,3]' - - runtimeOnly 'com.electronwill.night-config:yaml:3.6.6' } application { From 717d73289245aa368f5c72bcf4543db609f44c92 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 1 Sep 2023 22:23:23 +0200 Subject: [PATCH 042/462] Not needed. --- .../test/org/jdrupes/vmoperator/manager/package-info.java | 1 - 1 file changed, 1 deletion(-) delete mode 100644 org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/package-info.java diff --git a/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/package-info.java b/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/package-info.java deleted file mode 100644 index 7a983a6..0000000 --- a/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package org.jdrupes.vmoperator.manager; \ No newline at end of file From cdc37725ce5c1db90045a0c9b8c405a840a0148c Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 1 Sep 2023 22:23:51 +0200 Subject: [PATCH 043/462] Update. --- .../.settings/org.eclipse.jdt.core.prefs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs index c3d4bc7..4250f30 100644 --- a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs +++ b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs @@ -1,5 +1,5 @@ # -#Sat Aug 19 11:33:46 CEST 2023 +#Fri Sep 01 16:56:14 CEST 2023 org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert From 0419136a59c32a4ef1416630e417965d5acd66ac Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 1 Sep 2023 22:25:38 +0200 Subject: [PATCH 044/462] Use embedded YAML for load balancer configuration. --- .../vmoperator/manager/LoadBalancerReconciler.java | 11 +++++------ .../org/jdrupes/vmoperator/manager/Reconciler.java | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java index 8cf8f63..a9c0d2b 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java @@ -75,16 +75,16 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; Map model, VmChannel channel) throws IOException, TemplateException, ApiException { // Check if to be generated - @SuppressWarnings("unchecked") + @SuppressWarnings({ "unchecked", "PMD.AvoidDuplicateLiterals" }) var lbs = Optional.of(model) .map(m -> (Map) m.get("reconciler")) .map(c -> c.get(LOAD_BALANCER_SERVICE)).orElse(Boolean.FALSE); if (lbs instanceof Boolean isOn && !isOn) { return; } - if (!(lbs instanceof String)) { + if (!(lbs instanceof Map)) { logger.warning(() -> "\"" + LOAD_BALANCER_SERVICE - + "\" in configuration must be boolean or string but is " + + "\" in configuration must be boolean or mapping but is " + lbs.getClass() + "."); return; } @@ -110,9 +110,8 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; Object lbsConfig, VmChannel channel) { // Get metadata from config Map asmData = Collections.emptyMap(); - if (lbsConfig instanceof String config) { - asmData = (Map) new Yaml( - new SafeConstructor(new LoaderOptions())).load(config); + if (lbsConfig instanceof Map config) { + asmData = (Map) config; } var json = channel.client().getJSON(); JsonObject cfgMeta diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index 7815864..9777af1 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -106,12 +106,12 @@ import org.jgrapes.util.events.ConfigurationUpdate; * properties for the VM (defaults to 1.25). * * * `loadBalancerService`: If defined, causes a load balancer service - * to be created. This property may be a boolean or a string with nested + * to be created. This property may be a boolean or * YAML that defines additional labels or annotations to be merged * into the service defintion. Here's an example for using * [MetalLb](https://metallb.universe.tf/) as "internal load balancer": * ```yaml - * loadBalancerService: | + * loadBalancerService: * annotations: * metallb.universe.tf/loadBalancerIPs: 192.168.168.1 * metallb.universe.tf/ip-allocated-from-pool: single-common From 50f9b3ec1a648a2055e8f556f84a6c4081368de6 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 1 Sep 2023 22:31:53 +0200 Subject: [PATCH 045/462] Rename task. --- .../src/org.jdrupes.vmoperator.java-doc-conventions.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle index 1012df8..be163a9 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle @@ -28,7 +28,7 @@ task javadocResources(type: Copy) { } } -task java11doc (type: JavaExec) { +task apidocs (type: JavaExec) { // Does not work on JitPack, no /usr/bin/dot enabled = JavaVersion.current() == JavaVersion.VERSION_17 @@ -121,5 +121,5 @@ gradle.projectsEvaluated { .collect { tc -> tc.findByName("build") }.flatten() tasks.gitPublishReset.mustRunAfter subprojects.tasks .collect { tc -> tc.findByName("test") }.flatten() - tasks.gitPublishCopy.dependsOn java11doc + tasks.gitPublishCopy.dependsOn apidocs } From d5284a04a1441d2410e1d796e2b6cc49a9f86a90 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Sat, 2 Sep 2023 08:09:53 +0200 Subject: [PATCH 046/462] Update Readme.md --- dev-example/Readme.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dev-example/Readme.md b/dev-example/Readme.md index 99f8d0f..816aae0 100644 --- a/dev-example/Readme.md +++ b/dev-example/Readme.md @@ -3,17 +3,17 @@ The CRD must be deployed independently. Apart from that, the `kustomize.yaml` -* creates a small image repository and +* creates a small cdrom image repository and * deploys the operator in namespace `vmop-dev` with a replica of 0. This allows you to run the manager in your IDE. -The `kustomize.yaml` also changes the image repository for the -operator to a private repository for development. You have to +The `kustomize.yaml` also changes the container image repository for +the operator to a private repository for development. You have to either remove this or adapt it to your own repository if you also want to test your development version in a container. -If you want to run the unittests, this setup must be run with a private -repository and the private repository must match the one configured -for gradle pushImages. \ No newline at end of file +If you want to run the unittests, this setup *must* be run with a private +container image repository which must match the one configured +for gradle pushImages. From a315a86b46604a3d58d717e12b997f794633b965 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Sat, 2 Sep 2023 08:16:14 +0200 Subject: [PATCH 047/462] Update Readme.md --- dev-example/Readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dev-example/Readme.md b/dev-example/Readme.md index 816aae0..dfcd3e8 100644 --- a/dev-example/Readme.md +++ b/dev-example/Readme.md @@ -11,9 +11,9 @@ This allows you to run the manager in your IDE. The `kustomize.yaml` also changes the container image repository for the operator to a private repository for development. You have to -either remove this or adapt it to your own repository if you -also want to test your development version in a container. +adapt this to your own repository if you also want to test your +development version in a container. -If you want to run the unittests, this setup *must* be run with a private +If you want to run the unittests, this setup *must* be used with a private container image repository which must match the one configured for gradle pushImages. From 5c0d7df741525ecb9ab69cf3f4401ba8eb232086 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 2 Sep 2023 12:40:07 +0200 Subject: [PATCH 048/462] Add editorconfig. --- .editorconfig | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c38d0f7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +charset = utf-8 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{md,yml,yaml}] +indent_size = 2 +indent_style = space From eedce49dedd962aef6543021925279e27753681e Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 2 Sep 2023 12:47:04 +0200 Subject: [PATCH 049/462] Prepare for PRs. --- .github/workflows/gradle.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 629d13c..547c1a4 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,10 +1,8 @@ name: Java CI with Gradle on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] + push: {} + pull_request: {} permissions: contents: read From da929d894265541defdfa29153e8b833388475a1 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 2 Sep 2023 14:40:32 +0200 Subject: [PATCH 050/462] Add project group to all projects. --- build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7225e7e..c9e91ba 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,9 @@ plugins { id 'eclipse' } -project.group = 'org.jdrupes.vmoperator' +allprojects { + project.group = 'org.jdrupes.vmoperator' +} task stage { description = 'To be executed by CI, build and update JavaDoc.' From d5951565875b5ff27633ea5cf1eec89359be04d0 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 2 Sep 2023 14:48:57 +0200 Subject: [PATCH 051/462] Add badges. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e1c2df1..1895bbb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ [![Java CI with Gradle](https://github.com/mnlipp/VM-Operator/actions/workflows/gradle.yml/badge.svg)](https://github.com/mnlipp/VM-Operator/actions/workflows/gradle.yml) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/2277842dac894de4b663c6aa2779077e)](https://app.codacy.com/gh/mnlipp/VM-Operator/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) +![Latest Manager](https://img.shields.io/github/v/tag/mnlipp/vm-operator?filter=manager*&label=latest) +![Latest Runner](https://img.shields.io/github/v/tag/mnlipp/vm-operator?filter=runner-qemu*&label=latest) # Run Qemu in Kubernetes Pods From 431cb483daa0b88e931b047e56c5e356bfbf79e2 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 2 Sep 2023 20:58:46 +0200 Subject: [PATCH 052/462] Add indent for gradle. --- .editorconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.editorconfig b/.editorconfig index c38d0f7..d8c711a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,3 +8,7 @@ trim_trailing_whitespace = true [*.{md,yml,yaml}] indent_size = 2 indent_style = space + +[*.gradle] +indent_size = 4 +indent_style = space From 72beccc8cda8a193949588556b20af74b393c776 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 11 Sep 2023 17:50:21 +0200 Subject: [PATCH 053/462] Remove setup-machine-id. Causes problems and is not needed. --- .../src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch index 01fb4bb..379537b 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch @@ -1,6 +1,6 @@ FROM archlinux/archlinux -RUN systemd-firstboot --setup-machine-id +RUN systemd-firstboot RUN pacman -Suy --noconfirm \ && pacman -S --noconfirm which qemu-full virtiofsd \ From ffeaf31534f86606ea66135a39198ffa79667058 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 12 Sep 2023 11:48:45 +0200 Subject: [PATCH 054/462] Use branch names in version. --- .../org.jdrupes.vmoperator.java-common-conventions.gradle | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle index 0dba0c0..852423b 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle @@ -63,7 +63,12 @@ scmVersion { tag { def shortened = project.name.startsWith(project.group + ".") ? project.name.substring(project.group.length() + 1) : project.name - prefix = shortened.replace('.', '-') + "-" + var p = shortened.replace('.', '-') + "-" + if (grgit.branch.current.name != "main" + && !grgit.branch.current.name.startsWith("release")) { + p = p + grgit.branch.current.name.replace('/', '-') + "-" + } + prefix = p } } version = scmVersion.version From 2b471f3852538b15b2ea3e6b2c445d8a7aa27662 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 14 Sep 2023 09:56:28 +0200 Subject: [PATCH 055/462] More resources used by both runner and manager. --- .../manager/ConfigMapReconciler.java | 7 +++- .../jdrupes/vmoperator/manager/Constants.java | 9 ----- .../manager/LoadBalancerReconciler.java | 2 + .../jdrupes/vmoperator/manager/Manager.java | 2 +- .../vmoperator/manager/Reconciler.java | 4 +- .../manager/StatefuleSetReconciler.java | 2 + .../jdrupes/vmoperator/manager/VmWatcher.java | 14 ++++--- .../.settings/org.eclipse.jdt.core.prefs | 2 +- org.jdrupes.vmoperator.util/build.gradle | 1 + .../jdrupes/vmoperator/util/Constants.java | 37 +++++++++++++++++++ .../org/jdrupes/vmoperator/util}/GsonPtr.java | 2 +- .../src/org/jdrupes/vmoperator/util}/K8s.java | 2 +- 12 files changed, 62 insertions(+), 22 deletions(-) create mode 100644 org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Constants.java rename {org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager => org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util}/GsonPtr.java (99%) rename {org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager => org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util}/K8s.java (99%) diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java index 340a3cd..754669d 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java @@ -32,6 +32,9 @@ import java.io.IOException; import java.io.StringWriter; import java.util.Map; import java.util.logging.Logger; +import static org.jdrupes.vmoperator.manager.Constants.APP_NAME; +import static org.jdrupes.vmoperator.util.Constants.VM_OP_NAME; +import org.jdrupes.vmoperator.util.K8s; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; @@ -98,8 +101,8 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; DynamicKubernetesObject newCm) { ListOptions listOpts = new ListOptions(); listOpts.setLabelSelector( - "app.kubernetes.io/managed-by=" + Constants.VM_OP_NAME + "," - + "app.kubernetes.io/name=" + Constants.APP_NAME); + "app.kubernetes.io/managed-by=" + VM_OP_NAME + "," + + "app.kubernetes.io/name=" + APP_NAME); // Get pod, selected by label var podApi = new DynamicKubernetesApi("", "v1", "pods", client); var pods = podApi diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java index d1482b6..dd2774e 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java @@ -23,15 +23,6 @@ package org.jdrupes.vmoperator.manager; */ public class Constants { - /** The Constant VM_OP_NAME. */ - public static final String VM_OP_NAME = "vm-operator"; - - /** The Constant VM_OP_GROUP. */ - public static final String VM_OP_GROUP = "vmoperator.jdrupes.org"; - - /** The Constant VM_OP_KIND_VM. */ - public static final String VM_OP_KIND_VM = "VirtualMachine"; - /** The Constant APP_NAME. */ public static final String APP_NAME = "vm-runner"; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java index a9c0d2b..b8decd8 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java @@ -33,6 +33,8 @@ import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.logging.Logger; +import org.jdrupes.vmoperator.util.GsonPtr; +import org.jdrupes.vmoperator.util.K8s; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java index 5b55ca8..e26b813 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java @@ -30,7 +30,7 @@ import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; -import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME; +import static org.jdrupes.vmoperator.util.Constants.VM_OP_NAME; import org.jdrupes.vmoperator.util.FsdUtils; import org.jgrapes.core.Component; import org.jgrapes.core.Components; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index 9777af1..94b79dc 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -43,10 +43,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import static org.jdrupes.vmoperator.manager.Constants.VM_OP_GROUP; import org.jdrupes.vmoperator.manager.VmDefChanged.Type; +import static org.jdrupes.vmoperator.util.Constants.VM_OP_GROUP; import org.jdrupes.vmoperator.util.Convertions; import org.jdrupes.vmoperator.util.ExtendedObjectWrapper; +import org.jdrupes.vmoperator.util.GsonPtr; +import org.jdrupes.vmoperator.util.K8s; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.annotation.Handler; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefuleSetReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefuleSetReconciler.java index 6f6d09c..94764ad 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefuleSetReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefuleSetReconciler.java @@ -29,6 +29,8 @@ import java.io.IOException; import java.io.StringWriter; import java.util.Map; import java.util.logging.Logger; +import org.jdrupes.vmoperator.util.GsonPtr; +import org.jdrupes.vmoperator.util.K8s; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java index a304c0b..7ecce4c 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java @@ -44,9 +44,11 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; -import static org.jdrupes.vmoperator.manager.Constants.VM_OP_GROUP; -import static org.jdrupes.vmoperator.manager.Constants.VM_OP_KIND_VM; +import static org.jdrupes.vmoperator.manager.Constants.APP_NAME; import org.jdrupes.vmoperator.manager.VmDefChanged.Type; +import static org.jdrupes.vmoperator.util.Constants.VM_OP_GROUP; +import static org.jdrupes.vmoperator.util.Constants.VM_OP_KIND_VM; +import static org.jdrupes.vmoperator.util.Constants.VM_OP_NAME; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.Components; @@ -132,7 +134,7 @@ public class VmWatcher extends Component { for (var version : vmOpApiVersions) { coa.getAPIResources(VM_OP_GROUP, version) .getResources().stream() - .filter(r -> Constants.VM_OP_KIND_VM.equals(r.getKind())) + .filter(r -> VM_OP_KIND_VM.equals(r.getKind())) .findFirst() .ifPresent(crd -> watchVmDefs(crd, version)); } @@ -148,15 +150,15 @@ public class VmWatcher extends Component { // Get all known CR instances. coa.getAPIResources(VM_OP_GROUP, version) .getResources().stream() - .filter(r -> Constants.VM_OP_KIND_VM.equals(r.getKind())) + .filter(r -> VM_OP_KIND_VM.equals(r.getKind())) .findFirst() .ifPresent(crd -> known.addAll(getKnown(client, crd, version))); } ListOptions opts = new ListOptions(); opts.setLabelSelector( - "app.kubernetes.io/managed-by=" + Constants.VM_OP_NAME + "," - + "app.kubernetes.io/name=" + Constants.APP_NAME); + "app.kubernetes.io/managed-by=" + VM_OP_NAME + "," + + "app.kubernetes.io/name=" + APP_NAME); for (String resource : List.of("apps/v1/statefulsets", "v1/configmaps", "v1/secrets")) { var resParts = new LinkedList<>(List.of(resource.split("/"))); diff --git a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs index 4250f30..17fc9079 100644 --- a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs +++ b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs @@ -1,5 +1,5 @@ # -#Fri Sep 01 16:56:14 CEST 2023 +#Thu Sep 14 09:53:57 CEST 2023 org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert diff --git a/org.jdrupes.vmoperator.util/build.gradle b/org.jdrupes.vmoperator.util/build.gradle index 0dac0c5..f20afcb 100644 --- a/org.jdrupes.vmoperator.util/build.gradle +++ b/org.jdrupes.vmoperator.util/build.gradle @@ -10,4 +10,5 @@ plugins { dependencies { implementation 'org.freemarker:freemarker:[2.3.32,2.4)' + implementation 'io.kubernetes:client-java:[18.0.0,19)' } diff --git a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Constants.java b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Constants.java new file mode 100644 index 0000000..14fad65 --- /dev/null +++ b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Constants.java @@ -0,0 +1,37 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.util; + +/** + * Some constants. + */ +public class Constants { + + /** The Constant VM_OP_NAME. */ + public static final String VM_OP_NAME = "vm-operator"; + + /** The Constant VM_OP_GROUP. */ + public static final String VM_OP_GROUP = "vmoperator.jdrupes.org"; + + /** The Constant VM_OP_CRD_NAME. */ + public static final String VM_OP_CRD_NAME = "vms." + VM_OP_GROUP; + + /** The Constant VM_OP_KIND_VM. */ + public static final String VM_OP_KIND_VM = "VirtualMachine"; +} diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/GsonPtr.java b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java similarity index 99% rename from org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/GsonPtr.java rename to org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java index 7d0f403..22a76f3 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/GsonPtr.java +++ b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.jdrupes.vmoperator.manager; +package org.jdrupes.vmoperator.util; import com.google.gson.JsonArray; import com.google.gson.JsonElement; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/K8s.java b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/K8s.java similarity index 99% rename from org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/K8s.java rename to org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/K8s.java index efc304e..539a870 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/K8s.java +++ b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/K8s.java @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.jdrupes.vmoperator.manager; +package org.jdrupes.vmoperator.util; import io.kubernetes.client.common.KubernetesListObject; import io.kubernetes.client.common.KubernetesObject; From f50a4af46c9fd425bf8f6d26020dda004f87b496 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 14 Sep 2023 09:57:20 +0200 Subject: [PATCH 056/462] Namespace can be set for development. --- org.jdrupes.vmoperator.runner.qemu/config-sample.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml b/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml index 3e18871..461e79b 100644 --- a/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml +++ b/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml @@ -22,6 +22,11 @@ # the first time. Subsequent starts use the copy unless this option is set. # "updateTemplate": false + # The namespace that this runner runs in. Usually obtained from + # /var/run/secrets/kubernetes.io/serviceaccount/namespace. Should only + # be set when starting the runner during development e.g. from the IDE. + # "namespace": ... + # Define the VM (required) "vm": # The VM's name (required) From 74e05ce02326fc6ab022004956ab0b1f984afd2b Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 14 Sep 2023 14:14:07 +0200 Subject: [PATCH 057/462] Provide "Running" condition in status. --- deploy/crds/vms-crd.yaml | 34 ++- .../build.gradle | 2 + .../vmoperator/runner/qemu/Runner.java | 46 +++- .../vmoperator/runner/qemu/StatusUpdater.java | 244 ++++++++++++++++++ .../runner/qemu/events/RunnerStateChange.java | 42 ++- 5 files changed, 353 insertions(+), 15 deletions(-) create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index 0b24bd6..713b785 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -9,6 +9,8 @@ spec: - name: v1 served: true storage: true + subresources: + status: {} schema: openAPIV3Schema: type: object @@ -1372,10 +1374,28 @@ spec: - vm status: type: object - properties: + default: {} + properties: + cpus: + description: >- + Number of CPUs currently in use. + type: integer + default: 0 + ram: + description: >- + Amount of memory in use. + type: string + default: "0" conditions: description: >- List of component conditions observed + default: + - type: Running + status: "False" + observedGeneration: 1 + lastTransitionTime: "1970-01-01T00:00:00Z" + reason: Creation + message: "Creation of CR" type: array items: type: object @@ -1383,6 +1403,12 @@ spec: Information about the condition of a component. See https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#spec-and-status and https://github.com/kubernetes/apimachinery/blob/release-1.23/pkg/apis/meta/v1/types.go#L1432-L1492 + required: + - type + - status + - lastTransitionTime + - reason + - message properties: type: type: string @@ -1428,12 +1454,6 @@ spec: Message is a human readable message indicating details about the transition. This may be an empty string. default: "" - required: - - type - - status - - lastTransitionTime - - reason - - message # either Namespaced or Cluster scope: Namespaced names: diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle index 9f48a58..3e693bb 100644 --- a/org.jdrupes.vmoperator.runner.qemu/build.gradle +++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle @@ -18,6 +18,8 @@ dependencies { implementation 'commons-cli:commons-cli:1.5.0' implementation 'org.freemarker:freemarker:[2.3.32,2.4)' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:[2.15.1,3]' + + runtimeOnly 'org.slf4j:slf4j-jdk14:[2.0.7,3)' } application { diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index 1370e18..2e8216b 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -66,6 +66,7 @@ import org.jgrapes.core.annotation.Handler; import org.jgrapes.core.events.Start; import org.jgrapes.core.events.Started; import org.jgrapes.core.events.Stop; +import org.jgrapes.core.internal.EventProcessor; import org.jgrapes.io.NioDispatcher; import org.jgrapes.io.events.Input; import org.jgrapes.io.events.ProcessExited; @@ -90,6 +91,13 @@ import org.jgrapes.util.events.WatchFile; * * ![Runner state diagram](RunnerStates.svg) * + * The {@link Runner} associates an {@link EventProcessor} with the + * {@link Start} event. This "runner event processor" must be used + * for all events related to the application level function. Components + * that handle events from other sources (and thus event processors) + * must fire any resulting events on the runner event processor in order + * to maintain synchronization. + * * @startuml RunnerStates.svg * [*] --> Initializing * Initializing -> Initializing: InitialConfiguration/configure Runner @@ -149,8 +157,13 @@ import org.jgrapes.util.events.WatchFile; * error --> terminate * StartingProcess --> terminate: ProcessExited * + * state Stopped { + * state stopped <> * - * terminated --> [*] + * stopped --> [*] + * } + * + * terminated --> stopped * * @enduml * @@ -211,6 +224,7 @@ public class Runner extends Component { attach(new ProcessManager(channel())); attach(new SocketConnector(channel())); attach(qemuMonitor = new QemuMonitor(channel())); + attach(new StatusUpdater(channel())); // Configuration store with file in /etc/opt (default) File config = new File(cmdLine.getOptionValue('c', @@ -352,6 +366,11 @@ public class Runner extends Component { return; } + // Make sure to use thread specific client + // https://github.com/kubernetes-client/java/issues/100 + io.kubernetes.client.openapi.Configuration.setDefaultApiClient(null); + + // Prepare specific event pipeline to avoid concurrency. rep = newEventPipeline(); event.setAssociated(EventPipeline.class, rep); try { @@ -387,7 +406,8 @@ public class Runner extends Component { @Handler public void onStarted(Started event) { state = State.STARTING; - fire(new RunnerStateChange(state)); + rep.fire(new RunnerStateChange(state, "RunnerStarted", + "Runner has been started")); // Start first process if (config.vm.useTpm && swtpmDefinition != null) { startProcess(swtpmDefinition); @@ -476,7 +496,7 @@ public class Runner extends Component { */ @Handler public void onMonitorReady(MonitorReady event) { - fire(new RunnerConfigurationUpdate(config, state)); + rep.fire(new RunnerConfigurationUpdate(config, state)); } /** @@ -489,7 +509,8 @@ public class Runner extends Component { if (state == State.STARTING) { fire(new MonitorCommand(new QmpCont())); state = State.RUNNING; - fire(new RunnerStateChange(state)); + rep.fire(new RunnerStateChange(state, "VmStarted", + "Qemu has been configured and is continuing")); } } @@ -524,9 +545,22 @@ public class Runner extends Component { * @param event the event */ @Handler(priority = 10_000) - public void onStop(Stop event) { + public void onStopFirst(Stop event) { state = State.TERMINATING; - fire(new RunnerStateChange(state)); + rep.fire(new RunnerStateChange(state, "VmTerminating", + "The VM is being shut down")); + } + + /** + * On stop. + * + * @param event the event + */ + @Handler(priority = -10_000) + public void onStopLast(Stop event) { + state = State.STOPPED; + rep.fire(new RunnerStateChange(state, "VmStopped", + "The VM has been shut down")); } private void shutdown() { diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java new file mode 100644 index 0000000..e45b5e6 --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -0,0 +1,244 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.runner.qemu; + +import com.google.gson.JsonObject; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.apis.ApiextensionsV1Api; +import io.kubernetes.client.openapi.models.V1CustomResourceDefinitionVersion; +import io.kubernetes.client.util.Config; +import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; +import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Instant; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Level; +import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; +import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; +import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; +import static org.jdrupes.vmoperator.util.Constants.VM_OP_CRD_NAME; +import static org.jdrupes.vmoperator.util.Constants.VM_OP_GROUP; +import org.jgrapes.core.Channel; +import org.jgrapes.core.Component; +import org.jgrapes.core.annotation.Handler; +import org.jgrapes.core.events.Start; +import org.jgrapes.util.events.ConfigurationUpdate; +import org.jgrapes.util.events.InitialConfiguration; + +/** + * Updates the CR status. + */ +public class StatusUpdater extends Component { + + private static final Set RUNNING_STATES + = Set.of(State.RUNNING, State.TERMINATING); + + private String namespace; + private String vmName; + private DynamicKubernetesApi vmCrApi; + private long observedGeneration; + + /** + * Instantiates a new status updater. + * + * @param componentChannel the component channel + */ + public StatusUpdater(Channel componentChannel) { + super(componentChannel); + } + + /** + * On configuration update. + * + * @param event the event + */ + @Handler + @SuppressWarnings("unchecked") + public void onConfigurationUpdate(ConfigurationUpdate event) { + event.structured("/Runner").ifPresent(c -> { + if (event instanceof InitialConfiguration) { + namespace = (String) c.get("namespace"); + updateNamespace(); + vmName = Optional.ofNullable((Map) c.get("vm")) + .map(vm -> vm.get("name")).orElse(null); + } + }); + } + + private void updateNamespace() { + if (namespace == null) { + var path = Path + .of("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); + if (Files.isReadable(path)) { + try { + namespace = Files.lines(path).findFirst().orElse(null); + } catch (IOException e) { + logger.log(Level.WARNING, e, + () -> "Cannot read namespace."); + } + } + } + if (namespace == null) { + logger.warning(() -> "Namespace is unknown, some functions" + + " won't be available."); + } + } + + /** + * Handle the start event. + * + * @param event the event + */ + @Handler + @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", + "PMD.AvoidInstantiatingObjectsInLoops" }) + public void onStart(Start event) { + try { + var client = Config.defaultClient(); + var extsApi = new ApiextensionsV1Api(client); + var crds = extsApi.listCustomResourceDefinition(null, null, null, + "metadata.name=" + VM_OP_CRD_NAME, null, null, null, + null, null, null); + if (crds.getItems().isEmpty()) { + logger.warning(() -> "CRD is unknown, status will not" + + " be updated."); + return; + } + var crd = crds.getItems().get(0); + if (crd.getSpec().getVersions().stream() + .filter(v -> v.getSubresources() == null).findAny() + .isPresent()) { + logger.warning(() -> "You are using an old version of the CRD," + + " status will not be updated."); + return; + } + var crdPlural = crd.getSpec().getNames().getPlural(); + var vmOpApiVersions = crd.getSpec().getVersions().stream() + .map(V1CustomResourceDefinitionVersion::getName).toList(); + for (var apiVer : vmOpApiVersions) { + var api = new DynamicKubernetesApi(VM_OP_GROUP, apiVer, + crdPlural, client); + var res = api.get(namespace, vmName); + if (res.isSuccess()) { + vmCrApi = api; + observedGeneration + = res.getObject().getMetadata().getGeneration(); + break; + } + } + if (vmCrApi == null) { + logger.warning(() -> "VM's CR is unknown, status will not" + + " be updated."); + } + } catch (IOException | ApiException e) { + logger.log(Level.WARNING, e, () -> "Cannot access kubernetes: " + + e.getMessage()); + } + } + + @SuppressWarnings("PMD.AvoidDuplicateLiterals") + private JsonObject currentStatus(DynamicKubernetesObject vmCr) { + return vmCr.getRaw().getAsJsonObject("status").deepCopy(); + } + + /** + * On runner state changed. + * + * @param event the event + 8 * @throws ApiException the api exception + */ + @Handler + public void onRunnerStateChanged(RunnerStateChange event) + throws ApiException { + if (vmCrApi == null) { + return; + } + var vmCr = vmCrApi.get(namespace, vmName) + .throwsApiException().getObject(); + vmCrApi.updateStatus(vmCr, from -> { + JsonObject status = currentStatus(from); + status.getAsJsonArray("conditions").asList().stream() + .map(cond -> (JsonObject) cond) + .forEach(cond -> { + if ("Running".equals(cond.get("type").getAsString())) { + updateRunningCondition(event, from, cond); + } + }); + return status; + }); + } + + private void updateRunningCondition(RunnerStateChange event, + DynamicKubernetesObject from, JsonObject cond) { + boolean reportedRunning + = "True".equals(cond.get("status").getAsString()); + if (RUNNING_STATES.contains(event.state()) + && !reportedRunning) { + cond.addProperty("status", "True"); + cond.addProperty("lastTransitionTime", + Instant.now().toString()); + } + if (!RUNNING_STATES.contains(event.state()) + && reportedRunning) { + cond.addProperty("status", "False"); + cond.addProperty("lastTransitionTime", + Instant.now().toString()); + } + cond.addProperty("reason", event.reason()); + cond.addProperty("message", event.message()); + cond.addProperty("observedGeneration", + from.getMetadata().getGeneration()); + } + + /** + * On runner configuration update. + * + * @param event the event + * @throws ApiException + */ + @Handler + public void onRunnerConfigurationUpdate(RunnerConfigurationUpdate event) + throws ApiException { + if (vmCrApi == null) { + return; + } + // A change of the runner configuration is typically caused + // by a new version of the CR. So we observe the new CR. + var vmCr = vmCrApi.get(namespace, vmName).throwsApiException() + .getObject(); + if (vmCr.getMetadata().getGeneration() == observedGeneration) { + return; + } + vmCrApi.updateStatus(vmCr, from -> { + JsonObject status = currentStatus(from); + status.getAsJsonArray("conditions").asList().stream() + .map(cond -> (JsonObject) cond) + .filter( + cond -> "Running".equals(cond.get("type").getAsString())) + .forEach(cond -> cond.addProperty("observedGeneration", + from.getMetadata().getGeneration())); + return status; + }); + } + +} diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java index a827fa4..46fa1f8 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java @@ -19,6 +19,7 @@ package org.jdrupes.vmoperator.runner.qemu.events; import org.jgrapes.core.Channel; +import org.jgrapes.core.Components; import org.jgrapes.core.Event; /** @@ -30,19 +31,24 @@ public class RunnerStateChange extends Event { * The state. */ public enum State { - INITIALIZING, STARTING, RUNNING, TERMINATING + INITIALIZING, STARTING, RUNNING, TERMINATING, STOPPED } private final State state; + private final String reason; + private final String message; /** * Instantiates a new runner state change. * * @param channels the channels */ - public RunnerStateChange(State state, Channel... channels) { + public RunnerStateChange(State state, String reason, String message, + Channel... channels) { super(channels); this.state = state; + this.reason = reason; + this.message = message; } /** @@ -53,4 +59,36 @@ public class RunnerStateChange extends Event { public State state() { return state; } + + /** + * Gets the reason. + * + * @return the reason + */ + public String reason() { + return reason; + } + + /** + * Gets the message. + * + * @return the message + */ + public String message() { + return message; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(Components.objectName(this)) + .append(" [").append(state).append(": ").append(reason); + if (channels() != null) { + builder.append(", channels="); + builder.append(Channel.toString(channels())); + } + builder.append(']'); + return builder.toString(); + } + } From 9ba0dd8dc3c819b77e2c3cb353c5e7979b5d20c3 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 14 Sep 2023 14:45:06 +0200 Subject: [PATCH 058/462] Combine constants for easy use in templates. --- .../org/jdrupes/vmoperator/manager/ConfigMapReconciler.java | 2 +- .../src/org/jdrupes/vmoperator/manager/Constants.java | 2 +- .../src/org/jdrupes/vmoperator/manager/Manager.java | 2 +- .../src/org/jdrupes/vmoperator/manager/Reconciler.java | 2 +- .../src/org/jdrupes/vmoperator/manager/VmWatcher.java | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java index 754669d..15bdb76 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java @@ -33,7 +33,7 @@ import java.io.StringWriter; import java.util.Map; import java.util.logging.Logger; import static org.jdrupes.vmoperator.manager.Constants.APP_NAME; -import static org.jdrupes.vmoperator.util.Constants.VM_OP_NAME; +import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME; import org.jdrupes.vmoperator.util.K8s; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java index dd2774e..fabae37 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java @@ -21,7 +21,7 @@ package org.jdrupes.vmoperator.manager; /** * Some constants. */ -public class Constants { +public class Constants extends org.jdrupes.vmoperator.util.Constants { /** The Constant APP_NAME. */ public static final String APP_NAME = "vm-runner"; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java index e26b813..5b55ca8 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java @@ -30,7 +30,7 @@ import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; -import static org.jdrupes.vmoperator.util.Constants.VM_OP_NAME; +import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME; import org.jdrupes.vmoperator.util.FsdUtils; import org.jgrapes.core.Component; import org.jgrapes.core.Components; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index 94b79dc..ea43adf 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -43,8 +43,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import static org.jdrupes.vmoperator.manager.Constants.VM_OP_GROUP; import org.jdrupes.vmoperator.manager.VmDefChanged.Type; -import static org.jdrupes.vmoperator.util.Constants.VM_OP_GROUP; import org.jdrupes.vmoperator.util.Convertions; import org.jdrupes.vmoperator.util.ExtendedObjectWrapper; import org.jdrupes.vmoperator.util.GsonPtr; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java index 7ecce4c..ab2dc4a 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java @@ -45,10 +45,10 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import static org.jdrupes.vmoperator.manager.Constants.APP_NAME; +import static org.jdrupes.vmoperator.manager.Constants.VM_OP_GROUP; +import static org.jdrupes.vmoperator.manager.Constants.VM_OP_KIND_VM; +import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME; import org.jdrupes.vmoperator.manager.VmDefChanged.Type; -import static org.jdrupes.vmoperator.util.Constants.VM_OP_GROUP; -import static org.jdrupes.vmoperator.util.Constants.VM_OP_KIND_VM; -import static org.jdrupes.vmoperator.util.Constants.VM_OP_NAME; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.Components; From a045cba998fd125746f56ff852daa75562cd84c8 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 14 Sep 2023 14:55:50 +0200 Subject: [PATCH 059/462] Fix (make use of) transient dependencies. --- org.jdrupes.vmoperator.manager/build.gradle | 2 -- org.jdrupes.vmoperator.runner.qemu/build.gradle | 1 - org.jdrupes.vmoperator.util/build.gradle | 4 ++-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index 2c2dd7f..3a6dda3 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -16,8 +16,6 @@ dependencies { implementation project(':org.jdrupes.vmoperator.util') implementation 'commons-cli:commons-cli:1.5.0' - implementation 'org.freemarker:freemarker:[2.3.32,2.4)' - implementation 'io.kubernetes:client-java:[18.0.0,19)' runtimeOnly 'com.electronwill.night-config:yaml:[3.6.7,3.7)' runtimeOnly 'org.slf4j:slf4j-jdk14:[2.0.7,3)' diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle index 3e693bb..69f527f 100644 --- a/org.jdrupes.vmoperator.runner.qemu/build.gradle +++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle @@ -16,7 +16,6 @@ dependencies { implementation project(':org.jdrupes.vmoperator.util') implementation 'commons-cli:commons-cli:1.5.0' - implementation 'org.freemarker:freemarker:[2.3.32,2.4)' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:[2.15.1,3]' runtimeOnly 'org.slf4j:slf4j-jdk14:[2.0.7,3)' diff --git a/org.jdrupes.vmoperator.util/build.gradle b/org.jdrupes.vmoperator.util/build.gradle index f20afcb..3c772a8 100644 --- a/org.jdrupes.vmoperator.util/build.gradle +++ b/org.jdrupes.vmoperator.util/build.gradle @@ -9,6 +9,6 @@ plugins { } dependencies { - implementation 'org.freemarker:freemarker:[2.3.32,2.4)' - implementation 'io.kubernetes:client-java:[18.0.0,19)' + api 'org.freemarker:freemarker:[2.3.32,2.4)' + api 'io.kubernetes:client-java:[18.0.0,19)' } From 9d29266907f5c53448cf29feb8217a7d4bc61a2b Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 14 Sep 2023 18:59:18 +0200 Subject: [PATCH 060/462] Report current RAM usage in status. --- .../vmoperator/runner/qemu/StatusUpdater.java | 128 +++++++++++------- .../qemu/events/BalloonChangeEvent.java | 47 +++++++ .../runner/qemu/events/MonitorEvent.java | 5 +- 3 files changed, 130 insertions(+), 50 deletions(-) create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/BalloonChangeEvent.java diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index e45b5e6..3523af9 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -19,6 +19,8 @@ package org.jdrupes.vmoperator.runner.qemu; import com.google.gson.JsonObject; +import io.kubernetes.client.custom.Quantity; +import io.kubernetes.client.custom.Quantity.Format; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.ApiextensionsV1Api; import io.kubernetes.client.openapi.models.V1CustomResourceDefinitionVersion; @@ -26,6 +28,7 @@ import io.kubernetes.client.util.Config; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; import java.io.IOException; +import java.math.BigDecimal; import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; @@ -33,11 +36,13 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.logging.Level; +import org.jdrupes.vmoperator.runner.qemu.events.BalloonChangeEvent; import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; import static org.jdrupes.vmoperator.util.Constants.VM_OP_CRD_NAME; import static org.jdrupes.vmoperator.util.Constants.VM_OP_GROUP; +import org.jdrupes.vmoperator.util.GsonPtr; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.annotation.Handler; @@ -161,55 +166,6 @@ public class StatusUpdater extends Component { return vmCr.getRaw().getAsJsonObject("status").deepCopy(); } - /** - * On runner state changed. - * - * @param event the event - 8 * @throws ApiException the api exception - */ - @Handler - public void onRunnerStateChanged(RunnerStateChange event) - throws ApiException { - if (vmCrApi == null) { - return; - } - var vmCr = vmCrApi.get(namespace, vmName) - .throwsApiException().getObject(); - vmCrApi.updateStatus(vmCr, from -> { - JsonObject status = currentStatus(from); - status.getAsJsonArray("conditions").asList().stream() - .map(cond -> (JsonObject) cond) - .forEach(cond -> { - if ("Running".equals(cond.get("type").getAsString())) { - updateRunningCondition(event, from, cond); - } - }); - return status; - }); - } - - private void updateRunningCondition(RunnerStateChange event, - DynamicKubernetesObject from, JsonObject cond) { - boolean reportedRunning - = "True".equals(cond.get("status").getAsString()); - if (RUNNING_STATES.contains(event.state()) - && !reportedRunning) { - cond.addProperty("status", "True"); - cond.addProperty("lastTransitionTime", - Instant.now().toString()); - } - if (!RUNNING_STATES.contains(event.state()) - && reportedRunning) { - cond.addProperty("status", "False"); - cond.addProperty("lastTransitionTime", - Instant.now().toString()); - } - cond.addProperty("reason", event.reason()); - cond.addProperty("message", event.message()); - cond.addProperty("observedGeneration", - from.getMetadata().getGeneration()); - } - /** * On runner configuration update. * @@ -241,4 +197,78 @@ public class StatusUpdater extends Component { }); } + /** + * On runner state changed. + * + * @param event the event + * @throws ApiException the api exception + */ + @Handler + public void onRunnerStateChanged(RunnerStateChange event) + throws ApiException { + if (vmCrApi == null) { + return; + } + var vmCr = vmCrApi.get(namespace, vmName) + .throwsApiException().getObject(); + vmCrApi.updateStatus(vmCr, from -> { + JsonObject status = currentStatus(from); + status.getAsJsonArray("conditions").asList().stream() + .map(cond -> (JsonObject) cond) + .forEach(cond -> { + if ("Running".equals(cond.get("type").getAsString())) { + updateRunningCondition(event, from, cond); + } + }); + if (event.state() == State.STARTING) { + status.addProperty("ram", GsonPtr.to(from.getRaw()) + .getAsString("spec", "vm", "maximumRam").orElse("0")); + } + return status; + }); + } + + private void updateRunningCondition(RunnerStateChange event, + DynamicKubernetesObject from, JsonObject cond) { + boolean reportedRunning + = "True".equals(cond.get("status").getAsString()); + if (RUNNING_STATES.contains(event.state()) + && !reportedRunning) { + cond.addProperty("status", "True"); + cond.addProperty("lastTransitionTime", + Instant.now().toString()); + } + if (!RUNNING_STATES.contains(event.state()) + && reportedRunning) { + cond.addProperty("status", "False"); + cond.addProperty("lastTransitionTime", + Instant.now().toString()); + } + cond.addProperty("reason", event.reason()); + cond.addProperty("message", event.message()); + cond.addProperty("observedGeneration", + from.getMetadata().getGeneration()); + } + + /** + * On ballon change. + * + * @param event the event + * @throws ApiException + */ + @Handler + public void onBallonChange(BalloonChangeEvent event) throws ApiException { + if (vmCrApi == null) { + return; + } + var vmCr + = vmCrApi.get(namespace, vmName).throwsApiException().getObject(); + vmCrApi.updateStatus(vmCr, from -> { + JsonObject status = currentStatus(from); + status.addProperty("ram", + new Quantity(new BigDecimal(event.size()), Format.BINARY_SI) + .toSuffixedString()); + return status; + }); + } } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/BalloonChangeEvent.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/BalloonChangeEvent.java new file mode 100644 index 0000000..9cc67c8 --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/BalloonChangeEvent.java @@ -0,0 +1,47 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.runner.qemu.events; + +import com.fasterxml.jackson.databind.JsonNode; +import java.math.BigInteger; + +/** + * Signals a change of the balloon. + */ +public class BalloonChangeEvent extends MonitorEvent { + + /** + * Instantiates a new tray moved. + * + * @param kind the kind + * @param data the data + */ + public BalloonChangeEvent(Kind kind, JsonNode data) { + super(kind, data); + } + + /** + * Returns the actual value. + * + * @return the actual value + */ + public BigInteger size() { + return new BigInteger(data().get("actual").asText()); + } +} diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java index 28d2e4c..72647a1 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java @@ -32,7 +32,7 @@ public class MonitorEvent extends Event { * The kind of monitor event. */ public enum Kind { - READY, POWERDOWN, DEVICE_TRAY_MOVED + READY, POWERDOWN, DEVICE_TRAY_MOVED, BALLOON_CHANGE } private final Kind kind; @@ -55,6 +55,9 @@ public class MonitorEvent extends Event { case DEVICE_TRAY_MOVED: return Optional .of(new TrayMovedEvent(kind, response.get("data"))); + case BALLOON_CHANGE: + return Optional + .of(new BalloonChangeEvent(kind, response.get("data"))); default: return Optional .of(new MonitorEvent(kind, response.get("data"))); From 86ea626dd54d2562b8ab15ba6699cb6772cf5f0f Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 14 Sep 2023 18:59:46 +0200 Subject: [PATCH 061/462] Update. --- .../.settings/org.eclipse.jdt.core.prefs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs index 17fc9079..f78a58f 100644 --- a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs +++ b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs @@ -1,5 +1,5 @@ # -#Thu Sep 14 09:53:57 CEST 2023 +#Thu Sep 14 18:57:03 CEST 2023 org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert @@ -22,12 +22,12 @@ org.eclipse.jdt.core.formatter.blank_lines_after_package=1 org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true org.eclipse.jdt.core.formatter.comment.indent_root_tags=false +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert @@ -43,8 +43,8 @@ org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invoc org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=true +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 @@ -63,8 +63,8 @@ org.eclipse.jdt.core.formatter.alignment_for_type_parameters=16 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert From ce786ea70db2e9737edcec86b803a70929fcc88b Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 14 Sep 2023 19:03:45 +0200 Subject: [PATCH 062/462] Clear RAM usage on stop. --- .../src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index 3523af9..b72a478 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -223,6 +223,8 @@ public class StatusUpdater extends Component { if (event.state() == State.STARTING) { status.addProperty("ram", GsonPtr.to(from.getRaw()) .getAsString("spec", "vm", "maximumRam").orElse("0")); + } else if (event.state() == State.STOPPED) { + status.addProperty("ram", "0"); } return status; }); From 9c60153c8b61873172b11575d94fa7f17e86e010 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 14 Sep 2023 20:57:48 +0200 Subject: [PATCH 063/462] Set explicit encoding. --- buildSrc/.settings/org.eclipse.core.resources.prefs | 2 ++ buildSrc/.settings/org.eclipse.core.runtime.prefs | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 buildSrc/.settings/org.eclipse.core.resources.prefs create mode 100644 buildSrc/.settings/org.eclipse.core.runtime.prefs diff --git a/buildSrc/.settings/org.eclipse.core.resources.prefs b/buildSrc/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/buildSrc/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/buildSrc/.settings/org.eclipse.core.runtime.prefs b/buildSrc/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 0000000..5a0ad22 --- /dev/null +++ b/buildSrc/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +line.separator=\n From 77a4a274613b8de50e7bbda7d1b8bd521c310abc Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 14 Sep 2023 21:27:12 +0200 Subject: [PATCH 064/462] Update. --- .settings/org.eclipse.buildship.core.prefs | 2 +- .../.settings/org.eclipse.jdt.core.prefs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs index 33d02f4..d0fed22 100644 --- a/.settings/org.eclipse.buildship.core.prefs +++ b/.settings/org.eclipse.buildship.core.prefs @@ -5,7 +5,7 @@ connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.project.dir= eclipse.preferences.version=1 gradle.user.home= -java.home=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.362.b09-2.fc37.x86_64 +java.home= jvm.arguments= offline.mode=false override.workspace.settings=true diff --git a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs index f78a58f..300cb64 100644 --- a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs +++ b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs @@ -1,5 +1,5 @@ # -#Thu Sep 14 18:57:03 CEST 2023 +#Thu Sep 14 21:26:11 CEST 2023 org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert @@ -22,12 +22,12 @@ org.eclipse.jdt.core.formatter.blank_lines_after_package=1 org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.comment.indent_root_tags=false org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=false org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert @@ -43,8 +43,8 @@ org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invoc org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=true org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=true org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 @@ -63,8 +63,8 @@ org.eclipse.jdt.core.formatter.alignment_for_type_parameters=16 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert From 3eb8ca514ba05973a68dd20003678a745902f740 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 14 Sep 2023 22:07:01 +0200 Subject: [PATCH 065/462] Support eclipse configuration. --- buildSrc/build.gradle | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 8bad6d4..38f8ffd 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -9,6 +9,9 @@ plugins { // are build scripts in 'src/main' that automatically become available // as plugins in the main build. id 'groovy-gradle-plugin' + + // Apply eclipse plugin + id 'eclipse' } repositories { @@ -30,10 +33,41 @@ sourceSets { } } -/* java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } -*/ + +eclipse { + + project { + file { + // closure executed after .project content is loaded from existing file + // and before gradle build information is merged + beforeMerged { project -> + project.natures.clear() + project.buildCommands.clear() + } + + project.natures += 'org.eclipse.buildship.core.gradleprojectnature' + project.buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder' + } + } + + classpath { + downloadJavadoc = true + downloadSources = true + } + + jdt { + file { + withProperties { properties -> + def formatterPrefs = new Properties() + rootProject.file("gradle/org.eclipse.jdt.core.formatter.prefs") + .withInputStream { formatterPrefs.load(it) } + properties.putAll(formatterPrefs) + } + } + } +} From 7f512082f0096cfc0a7917aaf136502b50c25dc8 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 14 Sep 2023 22:08:15 +0200 Subject: [PATCH 066/462] Update. --- .project | 46 ++++++++++--------- buildSrc/.project | 13 ++++++ buildSrc/.settings/org.eclipse.jdt.core.prefs | 32 ++++++------- .../.settings/org.eclipse.jdt.core.prefs | 10 ++-- 4 files changed, 58 insertions(+), 43 deletions(-) diff --git a/.project b/.project index 505b6f1..e96d2da 100644 --- a/.project +++ b/.project @@ -2,37 +2,41 @@ VM-Operator - + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + net.sf.eclipsecs.core.CheckstyleBuilder + + + + + ch.acanda.eclipse.pmd.builder.PMDBuilder + + + + org.eclipse.jdt.core.javanature org.eclipse.buildship.core.gradleprojectnature net.sf.eclipsecs.core.CheckstyleNature ch.acanda.eclipse.pmd.builder.PMDNature - - - org.eclipse.jdt.core.javabuilder - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - net.sf.eclipsecs.core.CheckstyleBuilder - - - - ch.acanda.eclipse.pmd.builder.PMDBuilder - - - - 1 + 30 - org.eclipse.core.resources.regexFilterMatcher node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ diff --git a/buildSrc/.project b/buildSrc/.project index 8624d9f..effb550 100644 --- a/buildSrc/.project +++ b/buildSrc/.project @@ -5,7 +5,20 @@ + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + org.eclipse.jdt.groovy.core.groovyNature + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature diff --git a/buildSrc/.settings/org.eclipse.jdt.core.prefs b/buildSrc/.settings/org.eclipse.jdt.core.prefs index a15493a..68fda12 100644 --- a/buildSrc/.settings/org.eclipse.jdt.core.prefs +++ b/buildSrc/.settings/org.eclipse.jdt.core.prefs @@ -1,24 +1,22 @@ -# -#Sun May 07 20:03:15 CEST 2023 -org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.nullReference=warning -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=21 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=21 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error -org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.problem.nullReference=warning org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore -org.eclipse.jdt.core.compiler.source=17 -org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull -org.eclipse.jdt.core.compiler.release=disabled org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning -org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=21 diff --git a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs index 300cb64..672f85e 100644 --- a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs +++ b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs @@ -1,5 +1,5 @@ # -#Thu Sep 14 21:26:11 CEST 2023 +#Thu Sep 14 22:05:28 CEST 2023 org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert @@ -22,12 +22,12 @@ org.eclipse.jdt.core.formatter.blank_lines_after_package=1 org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true org.eclipse.jdt.core.formatter.comment.indent_root_tags=false +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert @@ -43,8 +43,8 @@ org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invoc org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=true +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 @@ -63,8 +63,8 @@ org.eclipse.jdt.core.formatter.alignment_for_type_parameters=16 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert From 8a7d9d6621992b8f755b37e3e05fb216f842b48c Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 15 Sep 2023 11:51:19 +0200 Subject: [PATCH 067/462] Report current CPUs in status. --- .../vmoperator/runner/qemu/CpuController.java | 50 +++++++++---------- .../vmoperator/runner/qemu/StatusUpdater.java | 23 +++++++++ .../qemu/events/HotpluggableCpuStatus.java | 40 +++++++++++++++ 3 files changed, 86 insertions(+), 27 deletions(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java index 6d8ccc1..f0face4 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java @@ -19,8 +19,8 @@ package org.jdrupes.vmoperator.runner.qemu; import com.fasterxml.jackson.databind.node.ObjectNode; -import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.Set; @@ -81,34 +81,28 @@ public class CpuController extends Component { /** * On monitor result. * - * @param result the result + * @param event the result */ @Handler - public void onHotpluggableCpuStatus(HotpluggableCpuStatus result) { - if (!result.successful()) { + public void onHotpluggableCpuStatus(HotpluggableCpuStatus event) { + if (!event.successful()) { logger.warning(() -> "Failed to get hotpluggable CPU status " - + "(won't adjust number of CPUs.): " + result.errorMessage()); + + "(won't adjust number of CPUs.): " + event.errorMessage()); } - - // Sort - List used = new ArrayList<>(); - List unused = new ArrayList<>(); - for (var itr = result.values().iterator(); itr.hasNext();) { - ObjectNode cpu = (ObjectNode) itr.next(); - if (cpu.has("qom-path")) { - used.add(cpu); - } else { - unused.add(cpu); - } - } - currentCpus = used.size(); if (desiredCpus == null) { return; } // Process - int diff = used.size() - desiredCpus; - diff = addCpus(used, unused, diff); - deleteCpus(used, diff); + currentCpus = event.usedCpus().size(); + int diff = currentCpus - desiredCpus; + if (diff == 0) { + return; + } + diff = addCpus(event.usedCpus(), event.unusedCpus(), diff); + removeCpus(event.usedCpus(), diff); + + // Report result + fire(new MonitorCommand(new QmpQueryHotpluggableCpus())); } @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") @@ -123,22 +117,24 @@ public class CpuController extends Component { } } int nextId = 1; - while (diff < 0 && !unused.isEmpty()) { + List remaining = new LinkedList<>(unused); + while (diff < 0 && !remaining.isEmpty()) { String id; do { id = "cpu-" + nextId++; } while (usedIds.contains(id)); - fire(new MonitorCommand(new QmpAddCpu(unused.get(0), id))); - unused.remove(0); + fire(new MonitorCommand(new QmpAddCpu(remaining.get(0), id))); + remaining.remove(0); diff += 1; } return diff; } @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") - private int deleteCpus(List used, int diff) { - while (diff > 0 && !used.isEmpty()) { - ObjectNode cpu = used.remove(0); + private int removeCpus(List used, int diff) { + List removable = new LinkedList<>(used); + while (diff > 0 && !removable.isEmpty()) { + ObjectNode cpu = removable.remove(0); String qomPath = cpu.get("qom-path").asText(); if (!qomPath.startsWith("/machine/peripheral/cpu-")) { continue; diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index b72a478..030075d 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -37,6 +37,7 @@ import java.util.Optional; import java.util.Set; import java.util.logging.Level; import org.jdrupes.vmoperator.runner.qemu.events.BalloonChangeEvent; +import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus; import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; @@ -223,8 +224,10 @@ public class StatusUpdater extends Component { if (event.state() == State.STARTING) { status.addProperty("ram", GsonPtr.to(from.getRaw()) .getAsString("spec", "vm", "maximumRam").orElse("0")); + status.addProperty("cpus", 1); } else if (event.state() == State.STOPPED) { status.addProperty("ram", "0"); + status.addProperty("cpus", 0); } return status; }); @@ -273,4 +276,24 @@ public class StatusUpdater extends Component { return status; }); } + + /** + * On ballon change. + * + * @param event the event + * @throws ApiException + */ + @Handler + public void onCpuChange(HotpluggableCpuStatus event) throws ApiException { + if (vmCrApi == null) { + return; + } + var vmCr + = vmCrApi.get(namespace, vmName).throwsApiException().getObject(); + vmCrApi.updateStatus(vmCr, from -> { + JsonObject status = currentStatus(from); + status.addProperty("cpus", event.usedCpus().size()); + return status; + }); + } } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/HotpluggableCpuStatus.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/HotpluggableCpuStatus.java index 3ed2e50..68641c9 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/HotpluggableCpuStatus.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/HotpluggableCpuStatus.java @@ -19,6 +19,10 @@ package org.jdrupes.vmoperator.runner.qemu.events; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand; /** @@ -26,6 +30,9 @@ import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand; */ public class HotpluggableCpuStatus extends MonitorResult { + private List usedCpus = new ArrayList<>(); + private List unusedCpus = new ArrayList<>(); + /** * Instantiates a new hotpluggable cpu result. * @@ -34,6 +41,39 @@ public class HotpluggableCpuStatus extends MonitorResult { */ public HotpluggableCpuStatus(QmpCommand command, JsonNode response) { super(command, response); + if (!successful()) { + return; + } + + // Sort + for (var itr = values().iterator(); itr.hasNext();) { + ObjectNode cpu = (ObjectNode) itr.next(); + if (cpu.has("qom-path")) { + usedCpus.add(cpu); + } else { + unusedCpus.add(cpu); + } + } + usedCpus = Collections.unmodifiableList(usedCpus); + unusedCpus = Collections.unmodifiableList(unusedCpus); + } + + /** + * Gets the used cpus. + * + * @return the usedCpus + */ + public List usedCpus() { + return usedCpus; + } + + /** + * Gets the unused cpus. + * + * @return the unusedCpus + */ + public List unusedCpus() { + return unusedCpus; } } From 2210dbcae24922e3d3845c81d2378045327e9f82 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 16 Sep 2023 11:43:51 +0200 Subject: [PATCH 068/462] Log handling errors. --- .../jdrupes/vmoperator/runner/qemu/Runner.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index 2e8216b..6ca3b2c 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -58,11 +58,13 @@ import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; import org.jdrupes.vmoperator.util.ExtendedObjectWrapper; import org.jdrupes.vmoperator.util.FsdUtils; +import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.Components; import org.jgrapes.core.EventPipeline; import org.jgrapes.core.TypedIdKey; import org.jgrapes.core.annotation.Handler; +import org.jgrapes.core.events.HandlingError; import org.jgrapes.core.events.Start; import org.jgrapes.core.events.Started; import org.jgrapes.core.events.Stop; @@ -238,6 +240,20 @@ public class Runner extends Component { fire(new WatchFile(config.toPath())); } + /** + * Log the exception when a handling error is reported. + * + * @param event the event + */ + @Handler(channels = Channel.class, priority = -10_000) + @SuppressWarnings("PMD.GuardLogStatement") + public void onHandlingError(HandlingError event) { + logger.log(Level.WARNING, event.throwable(), + () -> "Problem invoking handler with " + event.event() + ": " + + event.message()); + event.stop(); + } + /** * On configuration update. * From ea6751282c8147fbbe12ea48077e14bf31e0687d Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 16 Sep 2023 11:47:24 +0200 Subject: [PATCH 069/462] Take different approach to finding CR. Requires less privileges. --- .../vmoperator/runner/qemu/StatusUpdater.java | 117 +++++++++--------- .../jdrupes/vmoperator/util/Constants.java | 3 - 2 files changed, 61 insertions(+), 59 deletions(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index 030075d..af8ab7a 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -22,8 +22,10 @@ import com.google.gson.JsonObject; import io.kubernetes.client.custom.Quantity; import io.kubernetes.client.custom.Quantity.Format; import io.kubernetes.client.openapi.ApiException; -import io.kubernetes.client.openapi.apis.ApiextensionsV1Api; -import io.kubernetes.client.openapi.models.V1CustomResourceDefinitionVersion; +import io.kubernetes.client.openapi.apis.ApisApi; +import io.kubernetes.client.openapi.apis.CustomObjectsApi; +import io.kubernetes.client.openapi.models.V1APIGroup; +import io.kubernetes.client.openapi.models.V1GroupVersionForDiscovery; import io.kubernetes.client.util.Config; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; @@ -41,12 +43,13 @@ import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus; import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; -import static org.jdrupes.vmoperator.util.Constants.VM_OP_CRD_NAME; import static org.jdrupes.vmoperator.util.Constants.VM_OP_GROUP; +import static org.jdrupes.vmoperator.util.Constants.VM_OP_KIND_VM; import org.jdrupes.vmoperator.util.GsonPtr; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.annotation.Handler; +import org.jgrapes.core.events.HandlingError; import org.jgrapes.core.events.Start; import org.jgrapes.util.events.ConfigurationUpdate; import org.jgrapes.util.events.InitialConfiguration; @@ -73,6 +76,20 @@ public class StatusUpdater extends Component { super(componentChannel); } + /** + * On handling error. + * + * @param event the event + */ + @Handler(channels = Channel.class) + public void onHandlingError(HandlingError event) { + if (event.throwable() instanceof ApiException exc) { + logger.log(Level.WARNING, exc, + () -> "Problem accessing kubernetes: " + exc.getResponseBody()); + event.stop(); + } + } + /** * On configuration update. * @@ -114,51 +131,40 @@ public class StatusUpdater extends Component { * Handle the start event. * * @param event the event + * @throws IOException + * @throws ApiException */ @Handler @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", - "PMD.AvoidInstantiatingObjectsInLoops" }) - public void onStart(Start event) { - try { - var client = Config.defaultClient(); - var extsApi = new ApiextensionsV1Api(client); - var crds = extsApi.listCustomResourceDefinition(null, null, null, - "metadata.name=" + VM_OP_CRD_NAME, null, null, null, - null, null, null); - if (crds.getItems().isEmpty()) { - logger.warning(() -> "CRD is unknown, status will not" - + " be updated."); - return; + "PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals" }) + public void onStart(Start event) throws IOException, ApiException { + var client = Config.defaultClient(); + var apis = new ApisApi(client).getAPIVersions(); + var crdVersions = apis.getGroups().stream() + .filter(g -> g.getName().equals(VM_OP_GROUP)).findFirst() + .map(V1APIGroup::getVersions).stream().flatMap(l -> l.stream()) + .map(V1GroupVersionForDiscovery::getVersion).toList(); + var coa = new CustomObjectsApi(client); + for (var crdVersion : crdVersions) { + var crdApiRes = coa.getAPIResources(VM_OP_GROUP, + crdVersion).getResources().stream() + .filter(r -> VM_OP_KIND_VM.equals(r.getKind())).findFirst(); + if (crdApiRes.isEmpty()) { + continue; } - var crd = crds.getItems().get(0); - if (crd.getSpec().getVersions().stream() - .filter(v -> v.getSubresources() == null).findAny() - .isPresent()) { - logger.warning(() -> "You are using an old version of the CRD," - + " status will not be updated."); - return; + var crApi = new DynamicKubernetesApi(VM_OP_GROUP, + crdVersion, crdApiRes.get().getName(), client); + var vmCr = crApi.get(namespace, vmName).throwsApiException(); + if (vmCr.isSuccess()) { + vmCrApi = crApi; + observedGeneration + = vmCr.getObject().getMetadata().getGeneration(); + break; } - var crdPlural = crd.getSpec().getNames().getPlural(); - var vmOpApiVersions = crd.getSpec().getVersions().stream() - .map(V1CustomResourceDefinitionVersion::getName).toList(); - for (var apiVer : vmOpApiVersions) { - var api = new DynamicKubernetesApi(VM_OP_GROUP, apiVer, - crdPlural, client); - var res = api.get(namespace, vmName); - if (res.isSuccess()) { - vmCrApi = api; - observedGeneration - = res.getObject().getMetadata().getGeneration(); - break; - } - } - if (vmCrApi == null) { - logger.warning(() -> "VM's CR is unknown, status will not" - + " be updated."); - } - } catch (IOException | ApiException e) { - logger.log(Level.WARNING, e, () -> "Cannot access kubernetes: " - + e.getMessage()); + } + if (vmCrApi == null) { + logger.warning(() -> "Cannot find VM's CR, status will not" + + " be updated."); } } @@ -189,9 +195,8 @@ public class StatusUpdater extends Component { vmCrApi.updateStatus(vmCr, from -> { JsonObject status = currentStatus(from); status.getAsJsonArray("conditions").asList().stream() - .map(cond -> (JsonObject) cond) - .filter( - cond -> "Running".equals(cond.get("type").getAsString())) + .map(cond -> (JsonObject) cond).filter(cond -> "Running" + .equals(cond.get("type").getAsString())) .forEach(cond -> cond.addProperty("observedGeneration", from.getMetadata().getGeneration())); return status; @@ -202,7 +207,7 @@ public class StatusUpdater extends Component { * On runner state changed. * * @param event the event - * @throws ApiException the api exception + * @throws ApiException */ @Handler public void onRunnerStateChanged(RunnerStateChange event) @@ -210,8 +215,8 @@ public class StatusUpdater extends Component { if (vmCrApi == null) { return; } - var vmCr = vmCrApi.get(namespace, vmName) - .throwsApiException().getObject(); + var vmCr = vmCrApi.get(namespace, vmName).throwsApiException() + .getObject(); vmCrApi.updateStatus(vmCr, from -> { JsonObject status = currentStatus(from); status.getAsJsonArray("conditions").asList().stream() @@ -230,7 +235,7 @@ public class StatusUpdater extends Component { status.addProperty("cpus", 0); } return status; - }); + }).throwsApiException(); } private void updateRunningCondition(RunnerStateChange event, @@ -266,15 +271,15 @@ public class StatusUpdater extends Component { if (vmCrApi == null) { return; } - var vmCr - = vmCrApi.get(namespace, vmName).throwsApiException().getObject(); + var vmCr = vmCrApi.get(namespace, vmName).throwsApiException() + .getObject(); vmCrApi.updateStatus(vmCr, from -> { JsonObject status = currentStatus(from); status.addProperty("ram", new Quantity(new BigDecimal(event.size()), Format.BINARY_SI) .toSuffixedString()); return status; - }); + }).throwsApiException(); } /** @@ -288,12 +293,12 @@ public class StatusUpdater extends Component { if (vmCrApi == null) { return; } - var vmCr - = vmCrApi.get(namespace, vmName).throwsApiException().getObject(); + var vmCr = vmCrApi.get(namespace, vmName).throwsApiException() + .getObject(); vmCrApi.updateStatus(vmCr, from -> { JsonObject status = currentStatus(from); status.addProperty("cpus", event.usedCpus().size()); return status; - }); + }).throwsApiException(); } } diff --git a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Constants.java b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Constants.java index 14fad65..ba710d1 100644 --- a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Constants.java +++ b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/Constants.java @@ -29,9 +29,6 @@ public class Constants { /** The Constant VM_OP_GROUP. */ public static final String VM_OP_GROUP = "vmoperator.jdrupes.org"; - /** The Constant VM_OP_CRD_NAME. */ - public static final String VM_OP_CRD_NAME = "vms." + VM_OP_GROUP; - /** The Constant VM_OP_KIND_VM. */ public static final String VM_OP_KIND_VM = "VirtualMachine"; } From f24b6aca5245114385695022b707fea1b9743ab9 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 16 Sep 2023 11:48:20 +0200 Subject: [PATCH 070/462] Define RBAC for running in cluster. --- deploy/kustomization.yaml | 5 ++++- deploy/vmrunner-role-binding.yaml | 13 ++++++++++++ deploy/vmrunner-role.yaml | 20 +++++++++++++++++++ deploy/vmrunner-service-account.yaml | 6 ++++++ .../vmoperator/manager/runnerSts.ftl.yaml | 1 + 5 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 deploy/vmrunner-role-binding.yaml create mode 100644 deploy/vmrunner-role.yaml create mode 100644 deploy/vmrunner-service-account.yaml diff --git a/deploy/kustomization.yaml b/deploy/kustomization.yaml index 7193e0b..a988f88 100644 --- a/deploy/kustomization.yaml +++ b/deploy/kustomization.yaml @@ -7,4 +7,7 @@ resources: - vmop-role-binding.yaml - vmop-image-repository-pvc.yaml - vmop-config-map.yaml -- vmop-deployment.yaml \ No newline at end of file +- vmop-deployment.yaml +- vmrunner-role.yaml +- vmrunner-service-account.yaml +- vmrunner-role-binding.yaml diff --git a/deploy/vmrunner-role-binding.yaml b/deploy/vmrunner-role-binding.yaml new file mode 100644 index 0000000..6e3f75b --- /dev/null +++ b/deploy/vmrunner-role-binding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: vm-runner + labels: + app.kubernetes.io/name: vm-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: vm-runner +subjects: +- kind: ServiceAccount + name: vm-runner diff --git a/deploy/vmrunner-role.yaml b/deploy/vmrunner-role.yaml new file mode 100644 index 0000000..54e8742 --- /dev/null +++ b/deploy/vmrunner-role.yaml @@ -0,0 +1,20 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: vm-runner + labels: + app.kubernetes.io/name: vm-operator +rules: +- apiGroups: + - vmoperator.jdrupes.org + resources: + - vms + verbs: + - list + - get +- apiGroups: + - vmoperator.jdrupes.org + resources: + - vms/status + verbs: + - patch diff --git a/deploy/vmrunner-service-account.yaml b/deploy/vmrunner-service-account.yaml new file mode 100644 index 0000000..c876dad --- /dev/null +++ b/deploy/vmrunner-service-account.yaml @@ -0,0 +1,6 @@ +kind: ServiceAccount +apiVersion: v1 +metadata: + name: vm-runner + labels: + app.kubernetes.io/name: vm-operator diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml index 60834b9..ac1178a 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml @@ -140,6 +140,7 @@ spec: <#if cr.spec.affinity??> affinity: ${ cr.spec.affinity.toString() } + serviceAccountName: vm-runner volumeClaimTemplates: - metadata: namespace: ${ cr.metadata.namespace.asString } From 3b0fb02fbe1a3bfdd7357e2f09c71caab181c65c Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 16 Sep 2023 11:53:51 +0200 Subject: [PATCH 071/462] Improve error reporting. --- .../jdrupes/vmoperator/manager/Controller.java | 16 ++++++++++++++++ .../org/jdrupes/vmoperator/manager/Manager.java | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java index 998dc1d..28b078f 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java @@ -21,9 +21,11 @@ package org.jdrupes.vmoperator.manager; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.Configuration; import java.io.IOException; +import java.util.logging.Level; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.annotation.Handler; +import org.jgrapes.core.events.HandlingError; import org.jgrapes.core.events.Start; /** @@ -74,6 +76,20 @@ public class Controller extends Component { attach(new Reconciler(channel())); } + /** + * Special handling of {@link ApiException} thrown by handlers. + * + * @param event the event + */ + @Handler(channels = Channel.class) + public void onHandlingError(HandlingError event) { + if (event.throwable() instanceof ApiException exc) { + logger.log(Level.WARNING, exc, + () -> "Problem accessing kubernetes: " + exc.getResponseBody()); + event.stop(); + } + } + /** * Handle the start event. Has higher priority because it configures * the default Kubernetes client. diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java index 5b55ca8..a658846 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java @@ -32,9 +32,11 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME; import org.jdrupes.vmoperator.util.FsdUtils; +import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.Components; import org.jgrapes.core.annotation.Handler; +import org.jgrapes.core.events.HandlingError; import org.jgrapes.core.events.Stop; import org.jgrapes.io.NioDispatcher; import org.jgrapes.util.FileSystemWatcher; @@ -95,6 +97,20 @@ public class Manager extends Component { fire(new WatchFile(config.toPath())); } + /** + * Log the exception when a handling error is reported. + * + * @param event the event + */ + @Handler(channels = Channel.class, priority = -10_000) + @SuppressWarnings("PMD.GuardLogStatement") + public void onHandlingError(HandlingError event) { + logger.log(Level.WARNING, event.throwable(), + () -> "Problem invoking handler with " + event.event() + ": " + + event.message()); + event.stop(); + } + /** * On stop. * From 3825a4d3fd303da93860e6e7ba8bd899e4294d8a Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 16 Sep 2023 12:10:21 +0200 Subject: [PATCH 072/462] Remove explicit toolchain setting. --- buildSrc/build.gradle | 6 ------ 1 file changed, 6 deletions(-) diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 38f8ffd..8a1e6e1 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -33,12 +33,6 @@ sourceSets { } } -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - eclipse { project { From 1aecb82843fa4430013075e0c0e840f009b9e161 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 16 Sep 2023 13:20:42 +0200 Subject: [PATCH 073/462] Delay QEMU configuration until QMP is configured. Fixes #4. --- .../vmoperator/runner/qemu/Runner.java | 12 ++++--- .../runner/qemu/events/MonitorResult.java | 4 +++ .../runner/qemu/events/QmpConfigured.java | 36 +++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/QmpConfigured.java diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index 6ca3b2c..3ace2e5 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -52,7 +52,7 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCont; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; -import org.jdrupes.vmoperator.runner.qemu.events.MonitorReady; +import org.jdrupes.vmoperator.runner.qemu.events.QmpConfigured; import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; @@ -111,7 +111,8 @@ import org.jgrapes.util.events.WatchFile; * state "Start swtpm" as swtpm * state "Start qemu" as qemu * state "Open monitor" as monitor - * state "Configure" as configure + * state "Configure QMP" as waitForConfigured + * state "Configure QEMU" as configure * state success <> * state error <> * @@ -125,8 +126,11 @@ import org.jgrapes.util.events.WatchFile; * qemu --> monitor : FileChanged[monitor socket created] * * monitor: entry/fire OpenSocketConnection - * monitor --> configure: ClientConnected[for monitor] + * monitor --> waitForConfigured: ClientConnected[for monitor] * monitor -> error: ConnectError[for monitor] + * + * waitForConfigured: entry/fire QmpCapabilities + * waitForConfigured --> configure: QmpConfigured * * configure: entry/fire RunnerConfigurationUpdate * configure --> success: RunnerConfigurationUpdate (last handler)/fire cont command @@ -511,7 +515,7 @@ public class Runner extends Component { * @param event the event */ @Handler - public void onMonitorReady(MonitorReady event) { + public void onQmpConfigured(QmpConfigured event) { rep.fire(new RunnerConfigurationUpdate(config, state)); } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java index 83295dd..c0f55fe 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java @@ -21,6 +21,7 @@ package org.jdrupes.vmoperator.runner.qemu.events; import com.fasterxml.jackson.databind.JsonNode; import java.util.Optional; import org.jdrupes.vmoperator.runner.qemu.commands.QmpAddCpu; +import org.jdrupes.vmoperator.runner.qemu.commands.QmpCapabilities; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand; import org.jdrupes.vmoperator.runner.qemu.commands.QmpDelCpu; import org.jdrupes.vmoperator.runner.qemu.commands.QmpQueryHotpluggableCpus; @@ -44,6 +45,9 @@ public class MonitorResult extends Event { * @return the monitor result */ public static MonitorResult from(QmpCommand command, JsonNode response) { + if (command instanceof QmpCapabilities) { + return new QmpConfigured(command, response); + } if (command instanceof QmpQueryHotpluggableCpus) { return new HotpluggableCpuStatus(command, response); } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/QmpConfigured.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/QmpConfigured.java new file mode 100644 index 0000000..918177b --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/QmpConfigured.java @@ -0,0 +1,36 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.runner.qemu.events; + +import com.fasterxml.jackson.databind.JsonNode; +import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand; + +/** + * Signals that the QMP capabilities have been enabled. + */ +public class QmpConfigured extends MonitorResult { + + /** + * Instantiates a new monitor ready. + */ + public QmpConfigured(QmpCommand command, JsonNode response) { + super(command, response); + } + +} From b7ed2dcc9598660038f8db69d896d2c9529e0eaa Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 16 Sep 2023 21:30:46 +0200 Subject: [PATCH 074/462] Release from master. --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3c59936..d3f889d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,6 +21,7 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 + ref: refs/heads/master - name: Install graphviz run: sudo apt-get install graphviz - name: Install podman From 01c114bc18b43dccc7192be0b94a7ac7a285ae96 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 16 Sep 2023 22:06:37 +0200 Subject: [PATCH 075/462] Fix ref. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3f889d..3e6b3c9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - ref: refs/heads/master + ref: main - name: Install graphviz run: sudo apt-get install graphviz - name: Install podman From d42900afe72c976f68ca33d396795d21d1b9095e Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Fri, 22 Sep 2023 16:50:38 +0200 Subject: [PATCH 076/462] Feature/improve build (#9) --- .project | 46 ------------------- build.gradle | 4 +- ....vmoperator.java-common-conventions.gradle | 6 +-- ...pes.vmoperator.java-doc-conventions.gradle | 11 +++-- ...s.vmoperator.versioning-conventions.gradle | 28 +++++++++++ .../.settings/org.eclipse.jdt.core.prefs | 2 +- 6 files changed, 42 insertions(+), 55 deletions(-) delete mode 100644 .project create mode 100644 buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle diff --git a/.project b/.project deleted file mode 100644 index e96d2da..0000000 --- a/.project +++ /dev/null @@ -1,46 +0,0 @@ - - - VM-Operator - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - net.sf.eclipsecs.core.CheckstyleBuilder - - - - - ch.acanda.eclipse.pmd.builder.PMDBuilder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.buildship.core.gradleprojectnature - net.sf.eclipsecs.core.CheckstyleNature - ch.acanda.eclipse.pmd.builder.PMDNature - - - - 1 - - 30 - - org.eclipse.core.resources.regexFilterMatcher - node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ - - - - diff --git a/build.gradle b/build.gradle index c9e91ba..4fd0f66 100644 --- a/build.gradle +++ b/build.gradle @@ -5,8 +5,8 @@ buildscript { } plugins { - id 'org.ajoberstar.grgit' version '5.2.0' - id 'org.ajoberstar.git-publish' version '4.2.0' + id 'org.ajoberstar.grgit' version '5.2.0' apply false + id 'org.ajoberstar.git-publish' version '4.2.0' apply false id 'pl.allegro.tech.build.axion-release' version '1.15.0' apply false id 'org.jdrupes.vmoperator.java-doc-conventions' id 'eclipse' diff --git a/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle index 852423b..fa85d06 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle @@ -8,14 +8,14 @@ plugins { // Apply the java Plugin to add support for Java. id 'java' - // Git based versioning - id 'pl.allegro.tech.build.axion-release' - // Apply eclipse plugin id 'eclipse' // Access to git information id 'org.ajoberstar.grgit' + + // Apply the common versioning conventions. + id 'org.jdrupes.vmoperator.versioning-conventions' } repositories { diff --git a/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle index be163a9..95d7eff 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle @@ -1,10 +1,15 @@ plugins { - // Apply the common convention plugin for shared build configuration between library and application projects. - id 'org.jdrupes.vmoperator.java-common-conventions' - + // Apply the common versioning conventions. + id 'org.jdrupes.vmoperator.versioning-conventions' + id 'org.ajoberstar.git-publish' } +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + var docDestinationDir = file("${rootProject.buildDir}/javadoc") configurations { diff --git a/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle new file mode 100644 index 0000000..21006a3 --- /dev/null +++ b/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle @@ -0,0 +1,28 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This project uses @Incubating APIs which are subject to change. + */ + +plugins { + // Required by axion-release + id 'org.ajoberstar.grgit' + // Git based versioning + id 'pl.allegro.tech.build.axion-release' +} + +scmVersion { + versionIncrementer 'incrementMinor' + tag { + def shortened = project.name.startsWith(project.group + ".") ? + project.name.substring(project.group.length() + 1) : project.name + var p = shortened.replace('.', '-') + "-" + if (grgit.branch.current.name != "main" + && !grgit.branch.current.name.startsWith("release")) { + p = p + grgit.branch.current.name.replace('/', '-') + "-" + } + prefix = p + } +} +version = scmVersion.version +ext.isSnapshot = version.endsWith('-SNAPSHOT') diff --git a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs index 672f85e..e65abaa 100644 --- a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs +++ b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs @@ -1,5 +1,5 @@ # -#Thu Sep 14 22:05:28 CEST 2023 +#Fri Sep 22 15:21:52 CEST 2023 org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert From 7be1c407fecd1a7c79e6ad0326f831e97af51e8a Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Mon, 9 Oct 2023 22:13:33 +0200 Subject: [PATCH 077/462] Bugfix/cpu topology (#10) --- deploy/crds/vms-crd.yaml | 4 ++-- .../jdrupes/vmoperator/manager/runnerConfig.ftl.yaml | 4 ++-- org.jdrupes.vmoperator.runner.qemu/build.gradle | 2 +- .../jdrupes/vmoperator/runner/qemu/Configuration.java | 2 +- .../templates/Standard-VM-latest.ftl.yaml | 2 +- .../.settings/org.eclipse.jdt.core.prefs | 10 +++++----- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index 713b785..7e321da 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -1032,10 +1032,10 @@ spec: diesPerSocket: type: integer default: 0 - coresPerSocket: + coresPerDie: type: integer default: 0 - threadsPerSocket: + threadsPerCore: type: integer default: 0 currentCpus: diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml index dce5148..d49e705 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml @@ -88,9 +88,9 @@ data: maximumCpus: ${ parseQuantity(cr.spec.vm.maximumCpus.asString)?c } <#if cr.spec.vm.cpuTopology?? > - cpuSockets: ${ cr.spec.vm.cpuTopology.cpuSockets.asInt?c } + sockets: ${ cr.spec.vm.cpuTopology.sockets.asInt?c } diesPerSocket: ${ cr.spec.vm.cpuTopology.diesPerSocket.asInt?c } - coresPerSocket: ${ cr.spec.vm.cpuTopology.coresPerSocket.asInt?c } + coresPerDie: ${ cr.spec.vm.cpuTopology.coresPerDie.asInt?c } threadsPerCore: ${ cr.spec.vm.cpuTopology.threadsPerCore.asInt?c } <#if cr.spec.vm.currentCpus?? > diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle index 69f527f..8ead1f0 100644 --- a/org.jdrupes.vmoperator.runner.qemu/build.gradle +++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle @@ -43,7 +43,7 @@ task tagLatestArchImage(type: Exec) { dependsOn buildArchImage commandLine 'podman', 'tag', "${project.name}-arch:${project.version}",\ - "${project.name}:latest" + "${project.name}-arch:latest" } task buildLatestArchImage { diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java index 51ed47e..51fb3a5 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java @@ -106,7 +106,7 @@ public class Configuration implements Dto { public int currentCpus = 1; /** The cpu sockets. */ - public int cpuSockets; + public int sockets; /** The dies per socket. */ public int diesPerSocket; diff --git a/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml b/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml index f8c3716..cc8b505 100644 --- a/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml +++ b/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml @@ -87,7 +87,7 @@ - [ "-cpu", "${ vm.cpuModel }" ] <#if vm.maximumCpus gt 1> - [ "-smp", "1,maxcpus=${ vm.maximumCpus }\ - <#if vm.cpuSockets gt 0>,sockets=${ vm.cpuSockets }\ + <#if vm.sockets gt 0>,sockets=${ vm.sockets }\ <#if vm.diesPerSocket gt 0>,cores=${ vm.diesPerSocket }\ <#if vm.coresPerDie gt 0>,cores=${ vm.coresPerDie }\ <#if vm.threadsPerCore gt 0>,cores=${ vm.threadsPerCore }" ] diff --git a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs index e65abaa..68fe98d 100644 --- a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs +++ b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs @@ -1,5 +1,5 @@ # -#Fri Sep 22 15:21:52 CEST 2023 +#Mon Oct 09 18:47:59 CEST 2023 org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert @@ -22,12 +22,12 @@ org.eclipse.jdt.core.formatter.blank_lines_after_package=1 org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.comment.indent_root_tags=false org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=false org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert @@ -43,8 +43,8 @@ org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invoc org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=true org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=true org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 @@ -63,8 +63,8 @@ org.eclipse.jdt.core.formatter.alignment_for_type_parameters=16 org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert From 6491742eb04d43d66a93483ea82d38cda19ad61a Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Tue, 10 Oct 2023 12:57:13 +0200 Subject: [PATCH 078/462] Bugfix/cpu topology (#11) --- .../templates/Standard-VM-latest.ftl.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml b/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml index cc8b505..b21db8f 100644 --- a/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml +++ b/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml @@ -88,9 +88,9 @@ <#if vm.maximumCpus gt 1> - [ "-smp", "1,maxcpus=${ vm.maximumCpus }\ <#if vm.sockets gt 0>,sockets=${ vm.sockets }\ - <#if vm.diesPerSocket gt 0>,cores=${ vm.diesPerSocket }\ + <#if vm.diesPerSocket gt 0>,dies=${ vm.diesPerSocket }\ <#if vm.coresPerDie gt 0>,cores=${ vm.coresPerDie }\ - <#if vm.threadsPerCore gt 0>,cores=${ vm.threadsPerCore }" ] + <#if vm.threadsPerCore gt 0>,threads=${ vm.threadsPerCore }" ] <#if vm.accelerator != "none"> - [ "-accel", "${ vm.accelerator }" ] From ae3941707a8343762550406f30b9ccf828a8252b Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Sat, 21 Oct 2023 22:16:10 +0200 Subject: [PATCH 079/462] Feature/web gui (#12) Basic GUI functions (start/stop). --- .editorconfig | 2 +- .github/workflows/codeql.yml | 2 +- .gitignore | 4 + VM-Operator.png | Bin 0 -> 25290 bytes build.gradle | 1 + buildSrc/.project | 24 - buildSrc/build.gradle | 3 +- ....vmoperator.java-common-conventions.gradle | 16 - checkstyle.xml | 8 +- deploy/vmop-config-map.yaml | 8 +- dev-example/config.yaml | 29 + misc/javadoc.bottom.txt | 15 + org.jdrupes.vmoperator.common/.checkstyle | 10 + org.jdrupes.vmoperator.common/.eclipse-pmd | 7 + .../org.eclipse.buildship.core.prefs | 13 + .../org.eclipse.core.resources.prefs | 2 + .../.settings/org.eclipse.core.runtime.prefs | 2 + org.jdrupes.vmoperator.common/build.gradle | 14 + .../jdrupes/vmoperator/common}/Constants.java | 2 +- .../vmoperator/common}/Convertions.java | 2 +- .../org/jdrupes/vmoperator/common}/K8s.java | 49 +- .../vmoperator/common/package-info.java | 22 + .../.checkstyle | 10 + .../.eclipse-pmd | 7 + .../org.eclipse.buildship.core.prefs | 13 + .../org.eclipse.core.resources.prefs | 2 + .../.settings/org.eclipse.core.runtime.prefs | 2 + .../build.gradle | 14 + .../vmoperator/manager/events/StartVm.java | 52 + .../vmoperator/manager/events/StopVm.java | 52 + .../vmoperator/manager/events}/VmChannel.java | 10 +- .../manager/events}/VmDefChanged.java | 36 +- .../manager/events/package-info.java | 25 + org.jdrupes.vmoperator.manager/.checkstyle | 4 +- org.jdrupes.vmoperator.manager/build.gradle | 23 +- .../logging.properties | 5 +- .../vmoperator/manager/VM-Operator.png | Bin 0 -> 25290 bytes .../vmoperator/manager/VM-Operator.svg | 184 + .../vmoperator/manager/console-brand.ftl.html | 2 + .../jdrupes/vmoperator/manager/favicon.ico | Bin 0 -> 59789 bytes .../vmoperator/manager/l10n.properties | 19 + .../manager/ConfigMapReconciler.java | 4 +- .../jdrupes/vmoperator/manager/Constants.java | 2 +- .../vmoperator/manager/Controller.java | 97 + .../manager/LoadBalancerReconciler.java | 6 +- .../jdrupes/vmoperator/manager/Manager.java | 128 +- .../vmoperator/manager/Reconciler.java | 94 +- ...nciler.java => StatefulSetReconciler.java} | 10 +- .../jdrupes/vmoperator/manager/VmWatcher.java | 44 +- .../vmoperator/manager/package-info.java | 149 +- .../.checkstyle | 4 +- .../build.gradle | 2 +- .../vmoperator/runner/qemu/Configuration.java | 2 +- .../vmoperator/runner/qemu/StatusUpdater.java | 9 +- org.jdrupes.vmoperator.util/.checkstyle | 4 +- .../.settings/org.eclipse.jdt.core.prefs | 318 - org.jdrupes.vmoperator.util/build.gradle | 2 +- .../org/jdrupes/vmoperator/util/GsonPtr.java | 23 + .../jdrupes/vmoperator/util/package-info.java | 4 + org.jdrupes.vmoperator.vmconlet/.checkstyle | 10 + org.jdrupes.vmoperator.vmconlet/.eclipse-pmd | 7 + org.jdrupes.vmoperator.vmconlet/.gitignore | 4 + .../org.eclipse.buildship.core.prefs | 10 + .../org.eclipse.core.resources.prefs | 2 + .../.settings/org.eclipse.core.runtime.prefs | 2 + .../.settings/org.eclipse.jdt.ui.prefs | 63 + org.jdrupes.vmoperator.vmconlet/build.gradle | 54 + org.jdrupes.vmoperator.vmconlet/package.json | 1 + ...pes.webconsole.base.ConletComponentFactory | 1 + .../vmconlet/VmConlet-l10nBundles.ftl.js | 31 + .../vmconlet/VmConlet-preview.ftl.html | 5 + .../vmconlet/VmConlet-view.ftl.html | 84 + .../vmoperator/vmconlet/l10n.properties | 11 + .../vmoperator/vmconlet/l10n_de.properties | 17 + .../vmoperator/vmconlet/l10n_en.properties | 0 .../rollup.config.mjs | 35 + .../jdrupes/vmoperator/vmconlet/VmConlet.java | 247 + .../vmoperator/vmconlet/VmConletFactory.java | 54 + .../vmconlet/browser/VmConlet-functions.ts | 148 + .../vmconlet/browser/VmConlet-style.scss | 51 + .../vmconlet/browser/l10nBundles-stub.d.ts | 1 + .../vmoperator/vmconlet/package-info.java | 19 + org.jdrupes.vmoperator.vmconlet/tsconfig.json | 23 + package-lock.json | 10226 ++++++++++++++++ package.json | 33 + settings.gradle | 3 + 86 files changed, 12225 insertions(+), 514 deletions(-) create mode 100644 VM-Operator.png delete mode 100644 buildSrc/.project create mode 100644 org.jdrupes.vmoperator.common/.checkstyle create mode 100644 org.jdrupes.vmoperator.common/.eclipse-pmd create mode 100644 org.jdrupes.vmoperator.common/.settings/org.eclipse.buildship.core.prefs create mode 100644 org.jdrupes.vmoperator.common/.settings/org.eclipse.core.resources.prefs create mode 100644 org.jdrupes.vmoperator.common/.settings/org.eclipse.core.runtime.prefs create mode 100644 org.jdrupes.vmoperator.common/build.gradle rename {org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util => org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common}/Constants.java (96%) rename {org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util => org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common}/Convertions.java (99%) rename {org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util => org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common}/K8s.java (73%) create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/package-info.java create mode 100644 org.jdrupes.vmoperator.manager.events/.checkstyle create mode 100644 org.jdrupes.vmoperator.manager.events/.eclipse-pmd create mode 100644 org.jdrupes.vmoperator.manager.events/.settings/org.eclipse.buildship.core.prefs create mode 100644 org.jdrupes.vmoperator.manager.events/.settings/org.eclipse.core.resources.prefs create mode 100644 org.jdrupes.vmoperator.manager.events/.settings/org.eclipse.core.runtime.prefs create mode 100644 org.jdrupes.vmoperator.manager.events/build.gradle create mode 100644 org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/StartVm.java create mode 100644 org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/StopVm.java rename {org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager => org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events}/VmChannel.java (90%) rename {org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager => org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events}/VmDefChanged.java (64%) create mode 100644 org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/package-info.java create mode 100644 org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/VM-Operator.png create mode 100644 org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/VM-Operator.svg create mode 100644 org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-brand.ftl.html create mode 100644 org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/favicon.ico create mode 100644 org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/l10n.properties rename org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/{StatefuleSetReconciler.java => StatefulSetReconciler.java} (92%) delete mode 100644 org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs create mode 100644 org.jdrupes.vmoperator.vmconlet/.checkstyle create mode 100644 org.jdrupes.vmoperator.vmconlet/.eclipse-pmd create mode 100644 org.jdrupes.vmoperator.vmconlet/.gitignore create mode 100644 org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.buildship.core.prefs create mode 100644 org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.core.resources.prefs create mode 100644 org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.core.runtime.prefs create mode 100644 org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.jdt.ui.prefs create mode 100644 org.jdrupes.vmoperator.vmconlet/build.gradle create mode 100644 org.jdrupes.vmoperator.vmconlet/package.json create mode 100644 org.jdrupes.vmoperator.vmconlet/resources/META-INF/services/org.jgrapes.webconsole.base.ConletComponentFactory create mode 100644 org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-l10nBundles.ftl.js create mode 100644 org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-preview.ftl.html create mode 100644 org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html create mode 100644 org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n.properties create mode 100644 org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties create mode 100644 org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_en.properties create mode 100644 org.jdrupes.vmoperator.vmconlet/rollup.config.mjs create mode 100644 org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java create mode 100644 org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConletFactory.java create mode 100644 org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts create mode 100644 org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss create mode 100644 org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/l10nBundles-stub.d.ts create mode 100644 org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/package-info.java create mode 100644 org.jdrupes.vmoperator.vmconlet/tsconfig.json create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.editorconfig b/.editorconfig index d8c711a..7e375e1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,6 +9,6 @@ trim_trailing_whitespace = true indent_size = 2 indent_style = space -[*.gradle] +[*.{gradle,js,ts}] indent_size = 4 indent_style = space diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 32e0219..380981e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,7 +2,7 @@ name: "CodeQL" on: push: - branches: [ "main" ] + branches: [ "*" ] pull_request: # The branches below must be a subset of the branches above branches: [ "main" ] diff --git a/.gitignore b/.gitignore index 2595367..ca012b5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ bin .classpath .project org.eclipse.jdt.core.prefs +**/.externalToolBuilders/ + +# Node +**/node_modules/ diff --git a/VM-Operator.png b/VM-Operator.png new file mode 100644 index 0000000000000000000000000000000000000000..9ecb0229aafa381cf54ec1ad5790822efc5a87a1 GIT binary patch literal 25290 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
TsYbH?=7@4NCxsCnc(n$NN+8W4 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 VSk1K`^{oH^002ovPDHLkV1lp!?A`zX literal 0 HcmV?d00001 diff --git a/build.gradle b/build.gradle index 4fd0f66..1a11881 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,7 @@ plugins { id 'pl.allegro.tech.build.axion-release' version '1.15.0' apply false id 'org.jdrupes.vmoperator.java-doc-conventions' id 'eclipse' + id "com.github.node-gradle.node" version "7.0.1" } allprojects { diff --git a/buildSrc/.project b/buildSrc/.project deleted file mode 100644 index effb550..0000000 --- a/buildSrc/.project +++ /dev/null @@ -1,24 +0,0 @@ - - - buildSrc - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.buildship.core.gradleprojectbuilder - - - - - - org.eclipse.jdt.groovy.core.groovyNature - org.eclipse.jdt.core.javanature - org.eclipse.buildship.core.gradleprojectnature - - diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 8a1e6e1..a9fb634 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -45,7 +45,8 @@ eclipse { } project.natures += 'org.eclipse.buildship.core.gradleprojectnature' - project.buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder' + // Don't build, result not used by Eclipse anyway + // project.buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder' } } diff --git a/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle index fa85d06..50aebae 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle @@ -58,22 +58,6 @@ java { } } -scmVersion { - versionIncrementer 'incrementMinor' - tag { - def shortened = project.name.startsWith(project.group + ".") ? - project.name.substring(project.group.length() + 1) : project.name - var p = shortened.replace('.', '-') + "-" - if (grgit.branch.current.name != "main" - && !grgit.branch.current.name.startsWith("release")) { - p = p + grgit.branch.current.name.replace('/', '-') + "-" - } - prefix = p - } -} -version = scmVersion.version -ext.isSnapshot = version.endsWith('-SNAPSHOT') - jar { manifest { inputs.property("gitDescriptor", { grgit.describe(always: true) }) diff --git a/checkstyle.xml b/checkstyle.xml index 7fa3b95..b5a60d3 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -1,7 +1,7 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-brand.ftl.html b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-brand.ftl.html new file mode 100644 index 0000000..a81ee0a --- /dev/null +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-brand.ftl.html @@ -0,0 +1,2 @@ +${_("consoleTitle")} \ No newline at end of file diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/favicon.ico b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1b9443d9e421adaeb71ba0b2a897bdb8265e7f58 GIT binary patch literal 59789 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

<<)K*O@DMVx#G>+?xUUzKB_xkx8x+- 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 literal 0 HcmV?d00001 diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/l10n.properties b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/l10n.properties new file mode 100644 index 0000000..ec22a06 --- /dev/null +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/l10n.properties @@ -0,0 +1,19 @@ +# +# 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 . +# + +consoleTitle = VM-Operator diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java index 15bdb76..0fbb3a7 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java @@ -32,9 +32,11 @@ import java.io.IOException; import java.io.StringWriter; import java.util.Map; import java.util.logging.Logger; +import org.jdrupes.vmoperator.common.K8s; import static org.jdrupes.vmoperator.manager.Constants.APP_NAME; import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME; -import org.jdrupes.vmoperator.util.K8s; +import org.jdrupes.vmoperator.manager.events.VmChannel; +import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java index fabae37..bf51a59 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java @@ -21,7 +21,7 @@ package org.jdrupes.vmoperator.manager; /** * Some constants. */ -public class Constants extends org.jdrupes.vmoperator.util.Constants { +public class Constants extends org.jdrupes.vmoperator.common.Constants { /** The Constant APP_NAME. */ public static final String APP_NAME = "vm-runner"; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java index 28b078f..10bb1b2 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java @@ -18,15 +18,28 @@ package org.jdrupes.vmoperator.manager; +import io.kubernetes.client.custom.V1Patch; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.Configuration; +import io.kubernetes.client.util.Config; +import io.kubernetes.client.util.generic.options.PatchOptions; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.logging.Level; +import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; +import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM; +import org.jdrupes.vmoperator.common.K8s; +import org.jdrupes.vmoperator.manager.events.StartVm; +import org.jdrupes.vmoperator.manager.events.StopVm; +import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.annotation.Handler; import org.jgrapes.core.events.HandlingError; import org.jgrapes.core.events.Start; +import org.jgrapes.core.events.Stop; +import org.jgrapes.util.events.ConfigurationUpdate; /** * Implements a controller as defined in the @@ -66,6 +79,8 @@ import org.jgrapes.core.events.Start; */ public class Controller extends Component { + private String namespace; + /** * Creates a new instance. */ @@ -90,6 +105,20 @@ public class Controller extends Component { } } + /** + * Configure the component. + * + * @param event the event + */ + @Handler + public void onConfigurationUpdate(ConfigurationUpdate event) { + event.structured(componentPath()).ifPresent(c -> { + if (c.containsKey("namespace")) { + namespace = (String) c.get("namespace"); + } + }); + } + /** * Handle the start event. Has higher priority because it configures * the default Kubernetes client. @@ -103,5 +132,73 @@ public class Controller extends Component { // Make sure to use thread specific client // https://github.com/kubernetes-client/java/issues/100 Configuration.setDefaultApiClient(null); + + // Verify that a namespace has been configured + if (namespace == null) { + var path = Path + .of("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); + if (Files.isReadable(path)) { + namespace = Files.lines(path).findFirst().orElse(null); + } + } + if (namespace == null) { + logger.severe(() -> "Namespace to control not configured and" + + " no file in kubernetes directory."); + event.cancel(true); + fire(new Stop()); + return; + } + logger.fine(() -> "Controlling namespace \"" + namespace + "\"."); + } + + /** + * On start vm. + * + * @param event the event + * @throws ApiException the api exception + * @throws IOException Signals that an I/O exception has occurred. + */ + @Handler + public void onStartVm(StartVm event) throws ApiException, IOException { + patchRunning(event.name(), true); + } + + /** + * On stop vm. + * + * @param event the event + * @throws ApiException the api exception + * @throws IOException Signals that an I/O exception has occurred. + */ + @Handler + public void onStopVm(StopVm event) throws ApiException, IOException { + patchRunning(event.name(), false); + } + + private void patchRunning(String name, boolean running) + throws ApiException, IOException { + var crApi = K8s.crApi(Config.defaultClient(), VM_OP_GROUP, + VM_OP_KIND_VM, namespace, name); + if (crApi.isEmpty()) { + logger.warning(() -> "Trying to patch " + namespace + "/" + name + + " which does not exist."); + return; + } + + // Patch running + PatchOptions patchOpts = new PatchOptions(); + patchOpts.setFieldManager("kubernetes-java-kubectl-apply"); + var res = crApi.get().patch(namespace, name, + V1Patch.PATCH_FORMAT_JSON_PATCH, + new V1Patch("[{\"op\": \"replace\", \"path\": " + + "\"/spec/vm/state\", " + + "\"value\": \"" + (running ? "Running" : "Stopped") + + "\"}]"), + patchOpts); + if (!res.isSuccess()) { + logger.warning( + () -> "Cannot patch pod annotations: " + res.getStatus()); + } + } } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java index b8decd8..efa95f4 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java @@ -33,8 +33,10 @@ import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.logging.Logger; +import org.jdrupes.vmoperator.common.K8s; +import org.jdrupes.vmoperator.manager.events.VmChannel; +import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.util.GsonPtr; -import org.jdrupes.vmoperator.util.K8s; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; @@ -120,7 +122,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; = json.deserialize(json.serialize(asmData), JsonObject.class); // Get metadata from VM definition - var vmMeta = GsonPtr.to(channel.vmDefinition()).to("spec") + var vmMeta = GsonPtr.to(channel.vmDefinition().getRaw()).to("spec") .get(JsonObject.class, LOAD_BALANCER_SERVICE) .map(JsonObject::deepCopy).orElseGet(() -> new JsonObject()); diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java index a658846..f668c21 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java @@ -21,7 +21,12 @@ package org.jdrupes.vmoperator.manager; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.file.Files; +import java.util.Arrays; +import java.util.Collections; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; @@ -35,39 +40,34 @@ import org.jdrupes.vmoperator.util.FsdUtils; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.Components; +import org.jgrapes.core.NamedChannel; import org.jgrapes.core.annotation.Handler; import org.jgrapes.core.events.HandlingError; import org.jgrapes.core.events.Stop; +import org.jgrapes.http.HttpServer; +import org.jgrapes.http.InMemorySessionManager; +import org.jgrapes.http.LanguageSelector; +import org.jgrapes.http.events.Request; import org.jgrapes.io.NioDispatcher; +import org.jgrapes.io.util.PermitsPool; +import org.jgrapes.net.SocketServer; +import org.jgrapes.util.ComponentCollector; import org.jgrapes.util.FileSystemWatcher; import org.jgrapes.util.YamlConfigurationStore; import org.jgrapes.util.events.WatchFile; +import org.jgrapes.webconlet.locallogin.LoginConlet; +import org.jgrapes.webconsole.base.BrowserLocalBackedKVStore; +import org.jgrapes.webconsole.base.ConletComponentFactory; +import org.jgrapes.webconsole.base.ConsoleWeblet; +import org.jgrapes.webconsole.base.KVStoreBasedConsolePolicy; +import org.jgrapes.webconsole.base.PageResourceProviderFactory; +import org.jgrapes.webconsole.base.WebConsole; +import org.jgrapes.webconsole.rbac.RoleConfigurator; +import org.jgrapes.webconsole.rbac.RoleConletFilter; +import org.jgrapes.webconsole.vuejs.VueJsConsoleWeblet; /** - * The application class. In framework term, this is the root component. - * Two of its child components, the {@link Controller} and the WebGui - * implement user-visible functions. The others are used internally. - * - * ![Manager components](manager-components.svg) - * - * @startuml manager-components.svg - * skinparam component { - * BackGroundColor #FEFECE - * BorderColor #A80036 - * BorderThickness 1.25 - * BackgroundColor<> #F1F1F1 - * BorderColor<> #181818 - * BorderThickness<> 1 - * } - * - * [Manager] - * [Manager] *--> [Controller] - * [Manager] *--> [WebGui] - * [NioDispatcher] <> - * [Manager] *--> [NioDispatcher] <> - * [FileSystemWatcher] <> - * [Manager] *--> [FileSystemWatcher] <> - * @enduml + * The application class. */ public class Manager extends Component { @@ -79,22 +79,78 @@ public class Manager extends Component { * * @throws IOException Signals that an I/O exception has occurred. */ + @SuppressWarnings("PMD.TooFewBranchesForASwitchStatement") public Manager(CommandLine cmdLine) throws IOException { // Prepare component tree attach(new NioDispatcher()); - attach(new FileSystemWatcher(channel())); - attach(new Controller(channel())); + Channel mgrChannel = new NamedChannel("manager"); + attach(new FileSystemWatcher(mgrChannel)); + attach(new Controller(mgrChannel)); // Configuration store with file in /etc/opt (default) - File config = new File(cmdLine.getOptionValue('c', - "/etc/opt/" + VM_OP_NAME.replace("-", "") + "/config.yaml")); + File cfgFile = new File(cmdLine.getOptionValue('c', + "/etc/opt/" + VM_OP_NAME.replace("-", "") + "/config.yaml")) + .getCanonicalFile(); + logger.config(() -> "Using configuration from: " + cfgFile.getPath()); // Don't rely on night config to produce a good exception // for this simple case - if (!Files.isReadable(config.toPath())) { - throw new IOException("Cannot read configuration file " + config); + if (!Files.isReadable(cfgFile.toPath())) { + throw new IOException("Cannot read configuration file " + cfgFile); } - attach(new YamlConfigurationStore(channel(), config, false)); - fire(new WatchFile(config.toPath())); + attach(new YamlConfigurationStore(mgrChannel, cfgFile, false)); + fire(new WatchFile(cfgFile.toPath())); + + // Prepare GUI + Channel httpTransport = new NamedChannel("guiTransport"); + attach(new SocketServer(httpTransport) + .setConnectionLimiter(new PermitsPool(300)) + .setMinimalPurgeableTime(1000) + .setServerAddress(new InetSocketAddress(8080)) + .setName("GuiSocketServer")); + + // Create an HTTP server as converter between transport and application + // layer. + Channel httpChannel = new NamedChannel("guiHttp"); + HttpServer guiHttpServer = attach(new HttpServer(httpChannel, + httpTransport, Request.In.Get.class, Request.In.Post.class)); + guiHttpServer.setName("GuiHttpServer"); + + // Build HTTP application layer + guiHttpServer.attach(new InMemorySessionManager(httpChannel)); + guiHttpServer.attach(new LanguageSelector(httpChannel)); + URI rootUri; + try { + rootUri = new URI("/"); + } catch (URISyntaxException e) { + // Cannot happen + return; + } + ConsoleWeblet consoleWeblet = guiHttpServer + .attach(new VueJsConsoleWeblet(httpChannel, Channel.SELF, rootUri)) + .prependClassTemplateLoader(getClass()) + .prependResourceBundleProvider(getClass()) + .prependConsoleResourceProvider(getClass()); + consoleWeblet.setName("ConsoleWeblet"); + WebConsole console = consoleWeblet.console(); + console.attach(new BrowserLocalBackedKVStore( + console.channel(), consoleWeblet.prefix().getPath())); + console.attach(new KVStoreBasedConsolePolicy(console.channel())); + console.attach(new RoleConfigurator(console.channel())); + console.attach(new RoleConletFilter(console.channel())); + console.attach(new LoginConlet(console.channel())); + // Add all available page resource providers + console.attach(new ComponentCollector<>( + PageResourceProviderFactory.class, console.channel())); + // Add all available conlets + console.attach(new ComponentCollector<>( + ConletComponentFactory.class, console.channel(), type -> { + if (LoginConlet.class.getName().equals(type)) { + // Explicitly added, see above + return Collections.emptyList(); + } else { + return Arrays.asList(Collections.emptyMap()); + } + })); } /** @@ -148,10 +204,12 @@ public class Manager extends Component { @SuppressWarnings("PMD.SignatureDeclareThrowsException") public static void main(String[] args) { try { + // Instance logger is not available yet. var logger = Logger.getLogger(Manager.class.getName()); - logger.fine(() -> "Version: " + logger.config(() -> "Version: " + Manager.class.getPackage().getImplementationVersion()); - logger.fine(() -> "running on " + System.getProperty("java.vm.name") + logger.config(() -> "running on " + + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.version") + ")" + " from " + System.getProperty("java.vm.vendor")); @@ -165,7 +223,7 @@ public class Manager extends Component { // The Manager is the root component app = new Manager(cmd); - // Prepare Stop + // Prepare generation of Stop event Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { app.fire(new Stop()); diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index ea43adf..c3fee7d 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -33,7 +33,7 @@ import freemarker.template.TemplateModelException; import freemarker.template.TemplateNotFoundException; import io.kubernetes.client.custom.Quantity; import io.kubernetes.client.openapi.ApiException; -import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; +import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; @@ -43,12 +43,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import static org.jdrupes.vmoperator.manager.Constants.VM_OP_GROUP; -import org.jdrupes.vmoperator.manager.VmDefChanged.Type; -import org.jdrupes.vmoperator.util.Convertions; +import org.jdrupes.vmoperator.common.Convertions; +import org.jdrupes.vmoperator.manager.events.VmChannel; +import org.jdrupes.vmoperator.manager.events.VmDefChanged; +import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type; import org.jdrupes.vmoperator.util.ExtendedObjectWrapper; import org.jdrupes.vmoperator.util.GsonPtr; -import org.jdrupes.vmoperator.util.K8s; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.annotation.Handler; @@ -129,7 +129,7 @@ public class Reconciler extends Component { @SuppressWarnings("PMD.SingularField") private final Configuration fmConfig; private final ConfigMapReconciler cmReconciler; - private final StatefuleSetReconciler stsReconciler; + private final StatefulSetReconciler stsReconciler; private final LoadBalancerReconciler lbReconciler; @SuppressWarnings("PMD.UseConcurrentHashMap") private final Map config = new HashMap<>(); @@ -153,7 +153,7 @@ public class Reconciler extends Component { fmConfig.setClassForTemplateLoading(Reconciler.class, ""); cmReconciler = new ConfigMapReconciler(fmConfig); - stsReconciler = new StatefuleSetReconciler(fmConfig); + stsReconciler = new StatefulSetReconciler(fmConfig); lbReconciler = new LoadBalancerReconciler(fmConfig); } @@ -186,38 +186,65 @@ public class Reconciler extends Component { @SuppressWarnings("PMD.ConfusingTernary") public void onVmDefChanged(VmDefChanged event, VmChannel channel) throws ApiException, TemplateException, IOException { - var defMeta = event.object().getMetadata(); + // We're only interested in "spec" changes. + if (!event.specChanged()) { + return; + } // Ownership relationships takes care of deletions + var defMeta = event.vmDefinition().getMetadata(); if (event.type() == Type.DELETED) { logger.fine(() -> "VM \"" + defMeta.getName() + "\" deleted"); return; } - // Get full definition and associate with channel - var apiVersion = K8s.version(event.object().getApiVersion()); - DynamicKubernetesApi vmCrApi = new DynamicKubernetesApi(VM_OP_GROUP, - apiVersion, event.crd().getName(), channel.client()); - K8s.get(vmCrApi, defMeta).ifPresent(def -> channel - .setVmDefinition(patchCr(def.getRaw().deepCopy()))); - - // Reconcile - Map model = prepareModel(channel.vmDefinition()); + // Reconcile, use "augmented" vm definition for model + Map model = prepareModel(patchCr(event.vmDefinition())); var configMap = cmReconciler.reconcile(event, model, channel); model.put("cm", configMap.getRaw()); stsReconciler.reconcile(event, model, channel); lbReconciler.reconcile(event, model, channel); } + private DynamicKubernetesObject patchCr(DynamicKubernetesObject vmDef) { + var json = vmDef.getRaw().deepCopy(); + // Adjust cdromImage path + var disks + = GsonPtr.to(json).to("spec", "vm", "disks").get(JsonArray.class); + for (var disk : disks) { + var cdrom = (JsonObject) ((JsonObject) disk).get("cdrom"); + if (cdrom == null) { + continue; + } + String image = cdrom.get("image").getAsString(); + if (image.isEmpty()) { + continue; + } + try { + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + var imageUri = new URI("file://" + Constants.IMAGE_REPO_PATH + + "/").resolve(image); + if ("file".equals(imageUri.getScheme())) { + cdrom.addProperty("image", imageUri.getPath()); + } else { + cdrom.addProperty("image", imageUri.toString()); + } + } catch (URISyntaxException e) { + logger.warning(() -> "Invalid CDROM image: " + image); + } + } + return new DynamicKubernetesObject(json); + } + @SuppressWarnings("PMD.CognitiveComplexity") - private Map prepareModel(JsonObject vmDef) + private Map prepareModel(DynamicKubernetesObject vmDef) throws TemplateModelException { @SuppressWarnings("PMD.UseConcurrentHashMap") Map model = new HashMap<>(); model.put("managerVersion", Optional.ofNullable(Reconciler.class.getPackage() .getImplementationVersion()).orElse("(Unknown)")); - model.put("cr", vmDef); + model.put("cr", vmDef.getRaw()); model.put("constants", (TemplateHashModel) new DefaultObjectWrapperBuilder( Configuration.VERSION_2_3_32) @@ -274,33 +301,4 @@ public class Reconciler extends Component { return model; } - private JsonObject patchCr(JsonObject vmDef) { - // Adjust cdromImage path - var disks - = GsonPtr.to(vmDef).to("spec", "vm", "disks").get(JsonArray.class); - for (var disk : disks) { - var cdrom = (JsonObject) ((JsonObject) disk).get("cdrom"); - if (cdrom == null) { - continue; - } - String image = cdrom.get("image").getAsString(); - if (image.isEmpty()) { - continue; - } - try { - @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") - var imageUri = new URI("file://" + Constants.IMAGE_REPO_PATH - + "/").resolve(image); - if ("file".equals(imageUri.getScheme())) { - cdrom.addProperty("image", imageUri.getPath()); - } else { - cdrom.addProperty("image", imageUri.toString()); - } - } catch (URISyntaxException e) { - logger.warning(() -> "Invalid CDROM image: " + image); - } - } - return vmDef; - } - } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefuleSetReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java similarity index 92% rename from org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefuleSetReconciler.java rename to org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java index 94764ad..3cd6a39 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefuleSetReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java @@ -29,8 +29,10 @@ import java.io.IOException; import java.io.StringWriter; import java.util.Map; import java.util.logging.Logger; +import org.jdrupes.vmoperator.common.K8s; +import org.jdrupes.vmoperator.manager.events.VmChannel; +import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.util.GsonPtr; -import org.jdrupes.vmoperator.util.K8s; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; @@ -39,7 +41,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; * Delegee for reconciling the stateful set (effectively the pod). */ @SuppressWarnings("PMD.DataflowAnomalyAnalysis") -/* default */ class StatefuleSetReconciler { +/* default */ class StatefulSetReconciler { protected final Logger logger = Logger.getLogger(getClass().getName()); private final Configuration fmConfig; @@ -49,7 +51,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; * * @param fmConfig the fm config */ - public StatefuleSetReconciler(Configuration fmConfig) { + public StatefulSetReconciler(Configuration fmConfig) { this.fmConfig = fmConfig; } @@ -68,7 +70,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; throws IOException, TemplateException, ApiException { DynamicKubernetesApi stsApi = new DynamicKubernetesApi("apps", "v1", "statefulsets", channel.client()); - var metadata = event.object().getMetadata(); + var metadata = event.vmDefinition().getMetadata(); // Combine template and data and parse result var fmTemplate = fmConfig.getTemplate("runnerSts.ftl.yaml"); diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java index ab2dc4a..492ad40 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java @@ -44,11 +44,14 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; +import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; +import org.jdrupes.vmoperator.common.K8s; import static org.jdrupes.vmoperator.manager.Constants.APP_NAME; -import static org.jdrupes.vmoperator.manager.Constants.VM_OP_GROUP; import static org.jdrupes.vmoperator.manager.Constants.VM_OP_KIND_VM; import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME; -import org.jdrupes.vmoperator.manager.VmDefChanged.Type; +import org.jdrupes.vmoperator.manager.events.VmChannel; +import org.jdrupes.vmoperator.manager.events.VmDefChanged; +import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.Components; @@ -107,15 +110,8 @@ public class VmWatcher extends Component { namespaceToWatch = Files.lines(path).findFirst().orElse(null); } } - if (namespaceToWatch == null) { - logger.severe(() -> "Namespace to watch not configured and" - + " no file in kubernetes directory."); - event.cancel(true); - fire(new Stop()); - return; - } - logger - .fine(() -> "Controlling namespace \"" + namespaceToWatch + "\"."); + // Availability already checked by Controller.onStart + logger.fine(() -> "Watching namespace \"" + namespaceToWatch + "\"."); // Get all our API versions var client = Config.defaultClient(); @@ -140,8 +136,7 @@ public class VmWatcher extends Component { } } - @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops", - "PMD.CognitiveComplexity" }) + @SuppressWarnings("PMD.CognitiveComplexity") private void purge(ApiClient client, CustomObjectsApi coa, List vmOpApiVersions) throws ApiException { // Get existing CRs (VMs) @@ -161,6 +156,8 @@ public class VmWatcher extends Component { + "app.kubernetes.io/name=" + APP_NAME); for (String resource : List.of("apps/v1/statefulsets", "v1/configmaps", "v1/secrets")) { + @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops", + "PMD.AvoidDuplicateLiterals" }) var resParts = new LinkedList<>(List.of(resource.split("/"))); var group = resParts.size() == 3 ? resParts.poll() : ""; var version = resParts.poll(); @@ -271,13 +268,20 @@ public class VmWatcher extends Component { return; } - // Filter duplicates - if (!"DELETED".equals(item.type) && !channel - .setGeneration(item.object.getMetadata().getGeneration())) { - return; - } + // if (event.type() == Type.DELETED) { + + // Get full definition and associate with channel as backup + var apiVersion = K8s.version(item.object.getApiVersion()); + DynamicKubernetesApi vmCrApi = new DynamicKubernetesApi(VM_OP_GROUP, + apiVersion, vmsCrd.getName(), channel.client()); + var vmDef = K8s.get(vmCrApi, metadata); + vmDef.ifPresent(def -> channel.setVmDefinition(def)); + + // Create and fire event channel.pipeline().fire(new VmDefChanged(VmDefChanged.Type - .valueOf(item.type), vmsCrd, item.object), channel); + .valueOf(item.type), + channel.setGeneration(item.object.getMetadata().getGeneration()), + vmsCrd, vmDef.orElse(channel.vmDefinition())), channel); } /** @@ -289,7 +293,7 @@ public class VmWatcher extends Component { @Handler(priority = -10_000) public void onVmDefChanged(VmDefChanged event, VmChannel channel) { if (event.type() == Type.DELETED) { - channels.remove(event.object().getMetadata().getName()); + channels.remove(event.vmDefinition().getMetadata().getName()); } } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java index 6de13e3..48bc158 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java @@ -16,4 +16,151 @@ * along with this program. If not, see . */ -package org.jdrupes.vmoperator.manager; \ No newline at end of file +/** + * The following diagram shows the components of the manager application. + * + * In framework terms, the {@link org.jdrupes.vmoperator.manager.Manager} + * is the root component of the application. Two of its child components, + * the {@link org.jdrupes.vmoperator.manager.Controller} and the WebGui + * provide the functions that are apparent to the user. + * + * The position of the components in the component tree is important + * when writing the configuration file for the manager and therefore + * shown below. In order to keep the diagram readable, the + * components attached to the + * {@link org.jgrapes.webconsole.base.WebConsole} are shown in a + * separate diagram further down. + * + * ![Manager components](manager-components.svg) + * + * Component hierarchy of the web console: + * + * ![Web console components](console-components.svg) + * + * The components marked as "<<internal>>" have no + * configuration options or use their default values when used + * in this application. + * + * As an example, the following YAML configures a different port for the + * GUI and some users. The relationship to the component tree should + * be obvious. + * ``` + * "/Manager": + * "/GuiSocketServer": + * port: 8888 + * "/GuiHttpServer": + * "/ConsoleWeblet": + * "/WebConsole": + * "/LoginConlet": + * users: + * ... + * ``` + * + * Developers may also be interested in the usage of channels + * by the application's component: + * + * ![Main channels](app-channels.svg) + * + * @startuml manager-components.svg + * skinparam component { + * BackGroundColor #FEFECE + * BorderColor #A80036 + * BorderThickness 1.25 + * BackgroundColor<> #F1F1F1 + * BorderColor<> #181818 + * BorderThickness<> 1 + * } + * skinparam packageStyle rectangle + * + * Component NioDispatcher as NioDispatcher <> + * [Manager] *-up- [NioDispatcher] + * Component FileSystemWatcher as FileSystemWatcher <> + * [Manager] *-up- [FileSystemWatcher] + * Component YamlConfigurationStore as YamlConfigurationStore <> + * [Manager] *-left- [YamlConfigurationStore] + * [YamlConfigurationStore] *-right[hidden]- [Controller] + * + * [Manager] *-- [Controller] + * [Controller] *-- [VmWatcher] + * [Controller] *-- [Reconciler] + * [Controller] -right[hidden]- [GuiHttpServer] + * + * [Manager] *-down- [GuiSocketServer:8080] + * [Manager] *-- [GuiHttpServer] + * Component PreferencesStore as PreferencesStore <> + * [GuiHttpServer] *-up- [PreferencesStore] + * Component InMemorySessionManager as InMemorySessionManager <> + * [GuiHttpServer] *-up- [InMemorySessionManager] + * Component LanguageSelector as LanguageSelector <> + * [GuiHttpServer] *-right- [LanguageSelector] + * + * package "Conceptual WebConsole" { + * [ConsoleWeblet] *-- [WebConsole] + * } + * [GuiHttpServer] *-- [ConsoleWeblet] + * @enduml + * + * @startuml console-components.svg + * skinparam component { + * BackGroundColor #FEFECE + * BorderColor #A80036 + * BorderThickness 1.25 + * BackgroundColor<> #F1F1F1 + * BorderColor<> #181818 + * BorderThickness<> 1 + * } + * skinparam packageStyle rectangle + * + * Component BrowserLocalBackedKVStore as BrowserLocalBackedKVStore <> + * [WebConsole] *-up- [BrowserLocalBackedKVStore] + * Component KVStoreBasedConsolePolicy as KVStoreBasedConsolePolicy <> + * [WebConsole] *-up- [KVStoreBasedConsolePolicy] + * + * [WebConsole] *-- [RoleConfigurator] + * [WebConsole] *-- [RoleConletFilter] + * [WebConsole] *-left- [LoginConlet] + * + * Component "ComponentCollector\nfor page resources" as cpr <> + * [WebConsole] *-- [cpr] + * Component "ComponentCollector\nfor conlets" as cc <> + * [WebConsole] *-- [cc] + * + * package "Providers and Conlets" { + * [Some component] + * } + * + * [cpr] *-- [Some component] + * [cc] *-- [Some component] + * @enduml + * + * @startuml app-channels.svg + * skinparam packageStyle rectangle + * + * () "manager" as mgr + * mgr .left. [FileSystemWatcher] + * mgr .right. [YamlConfigurationStore] + * mgr .. [Controller] + * mgr .up. [VmWatcher] + * mgr .. [Reconciler] + * + * () "guiTransport" as hT + * hT .up. [GuiSocketServer:8080] + * hT .down. [GuiHttpServer] + * + * [YamlConfigurationStore] -right[hidden]- hT + * + * () "guiHttp" as http + * http .up. [GuiHttpServer] + * + * [PreferencesStore] .right. http + * [InMemorySessionManager] .up. http + * [LanguageSelector] .up. http + * + * package "Conceptual WebConsole" { + * [ConsoleWeblet] .left. http + * [ConsoleWeblet] *-down- [WebConsole] + * } + * + * @enduml + */ +package org.jdrupes.vmoperator.manager; diff --git a/org.jdrupes.vmoperator.runner.qemu/.checkstyle b/org.jdrupes.vmoperator.runner.qemu/.checkstyle index 2add381..7f2c604 100644 --- a/org.jdrupes.vmoperator.runner.qemu/.checkstyle +++ b/org.jdrupes.vmoperator.runner.qemu/.checkstyle @@ -1,10 +1,10 @@ - + - + diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle index 8ead1f0..9221ba7 100644 --- a/org.jdrupes.vmoperator.runner.qemu/build.gradle +++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle @@ -13,7 +13,7 @@ dependencies { implementation 'org.jgrapes:org.jgrapes.io:[2.7.0,3)' implementation 'org.jgrapes:org.jgrapes.http:[3.1.0,4)' implementation 'org.jgrapes:org.jgrapes.util:[1.31.0,2)' - implementation project(':org.jdrupes.vmoperator.util') + implementation project(':org.jdrupes.vmoperator.common') implementation 'commons-cli:commons-cli:1.5.0' implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:[2.15.1,3]' diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java index 51fb3a5..bbee56e 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java @@ -28,7 +28,7 @@ import java.util.Set; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; -import org.jdrupes.vmoperator.util.Convertions; +import org.jdrupes.vmoperator.common.Convertions; import org.jdrupes.vmoperator.util.Dto; import org.jdrupes.vmoperator.util.FsdUtils; diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index af8ab7a..19e252f 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -38,13 +38,13 @@ import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.logging.Level; +import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; +import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM; import org.jdrupes.vmoperator.runner.qemu.events.BalloonChangeEvent; import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus; import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; -import static org.jdrupes.vmoperator.util.Constants.VM_OP_GROUP; -import static org.jdrupes.vmoperator.util.Constants.VM_OP_KIND_VM; import org.jdrupes.vmoperator.util.GsonPtr; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; @@ -138,6 +138,9 @@ public class StatusUpdater extends Component { @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals" }) public void onStart(Start event) throws IOException, ApiException { + if (namespace == null) { + return; + } var client = Config.defaultClient(); var apis = new ApisApi(client).getAPIVersions(); var crdVersions = apis.getGroups().stream() @@ -154,7 +157,7 @@ public class StatusUpdater extends Component { } var crApi = new DynamicKubernetesApi(VM_OP_GROUP, crdVersion, crdApiRes.get().getName(), client); - var vmCr = crApi.get(namespace, vmName).throwsApiException(); + var vmCr = crApi.get(namespace, vmName); if (vmCr.isSuccess()) { vmCrApi = crApi; observedGeneration diff --git a/org.jdrupes.vmoperator.util/.checkstyle b/org.jdrupes.vmoperator.util/.checkstyle index 2add381..7f2c604 100644 --- a/org.jdrupes.vmoperator.util/.checkstyle +++ b/org.jdrupes.vmoperator.util/.checkstyle @@ -1,10 +1,10 @@ - + - + diff --git a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs b/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 68fe98d..0000000 --- a/org.jdrupes.vmoperator.util/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,318 +0,0 @@ -# -#Mon Oct 09 18:47:59 CEST 2023 -org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines -org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert -org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=false -org.eclipse.jdt.core.formatter.indentation.size=4 -org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert -org.eclipse.jdt.core.formatter.continuation_indentation=2 -org.eclipse.jdt.core.formatter.blank_lines_after_package=1 -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true -org.eclipse.jdt.core.formatter.comment.indent_root_tags=false -org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on -org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert -org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert -org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=16 -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert -org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=true -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 -org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 -org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false -org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=20 -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true -org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line -org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_type_parameters=16 -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false -org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert -org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false -org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines -org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.align_type_members_on_columns=false -org.eclipse.jdt.core.formatter.alignment_for_module_statements=16 -org.eclipse.jdt.core.formatter.alignment_for_assignment=20 -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 -org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false -org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert -org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.comment.format_header=false -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=20 -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.alignment_for_method_declaration=16 -org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true -org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 -org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert -org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=16 -org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=20 -org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=true -org.eclipse.jdt.core.compiler.source=17 -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert -org.eclipse.jdt.core.formatter.comment.format_source_code=false -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert -org.eclipse.jdt.core.formatter.blank_lines_before_field=0 -org.eclipse.jdt.core.formatter.blank_lines_before_method=1 -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert -org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 -org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert -org.eclipse.jdt.core.formatter.comment.format_html=false -org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines -org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 -org.eclipse.jdt.core.formatter.indent_empty_lines=false -org.eclipse.jdt.core.formatter.alignment_for_type_arguments=16 -org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=20 -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false -org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert -org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert -org.eclipse.jdt.core.compiler.compliance=17 -org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true -org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=16 -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert -org.eclipse.jdt.core.formatter.join_lines_in_comments=false -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert -org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 -org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines -org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 -org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines -org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert -org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off -org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16 -org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 -org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert -org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines -org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert -org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=true -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert -org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true -org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert -org.eclipse.jdt.core.formatter.comment.line_length=80 -org.eclipse.jdt.core.formatter.use_on_off_tags=false -org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert -org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert -org.eclipse.jdt.core.formatter.alignment_for_binary_expression=20 -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert -org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line -org.eclipse.jdt.core.formatter.compact_else_if=true -org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=20 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false -org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert -org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 -org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert -org.eclipse.jdt.core.formatter.comment.format_line_comments=true -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=84 -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert -org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.formatter.join_wrapped_lines=false -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true -org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert -org.eclipse.jdt.core.formatter.tabulation.size=4 -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=1 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 -org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=true -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert -org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert -eclipse.preferences.version=1 -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert -org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=20 -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.comment.format_block_comments=true -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert -org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true -org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert -org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.blank_lines_before_package=0 -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert -org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter -org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true -org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.tabulation.char=space -org.eclipse.jdt.core.formatter.lineSplit=80 -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert diff --git a/org.jdrupes.vmoperator.util/build.gradle b/org.jdrupes.vmoperator.util/build.gradle index 3c772a8..5f2b065 100644 --- a/org.jdrupes.vmoperator.util/build.gradle +++ b/org.jdrupes.vmoperator.util/build.gradle @@ -10,5 +10,5 @@ plugins { dependencies { api 'org.freemarker:freemarker:[2.3.32,2.4)' - api 'io.kubernetes:client-java:[18.0.0,19)' + api 'com.google.code.gson:gson:2.10.1' } diff --git a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java index 22a76f3..c16b3e0 100644 --- a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java +++ b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java @@ -22,6 +22,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; +import java.math.BigInteger; import java.util.Optional; import java.util.function.Supplier; @@ -175,6 +176,17 @@ public class GsonPtr { .map(JsonPrimitive::getAsInt); } + /** + * Returns the Integer value of the selected {@link JsonPrimitive}. + * + * @param selectors the selectors + * @return the as string + */ + public Optional getAsBigInteger(Object... selectors) { + return get(JsonPrimitive.class, selectors) + .map(JsonPrimitive::getAsBigInteger); + } + /** * Returns the Long value of the selected {@link JsonPrimitive}. * @@ -186,6 +198,17 @@ public class GsonPtr { .map(JsonPrimitive::getAsLong); } + /** + * Returns the boolean value of the selected {@link JsonPrimitive}. + * + * @param selectors the selectors + * @return the boolean + */ + public Optional getAsBoolean(Object... selectors) { + return get(JsonPrimitive.class, selectors) + .map(JsonPrimitive::getAsBoolean); + } + /** * Sets the selected value. This pointer must point to a * {@link JsonObject} or {@link JsonArray}. The selector must diff --git a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/package-info.java b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/package-info.java index 24ffeb7..e1ce2f7 100644 --- a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/package-info.java +++ b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/package-info.java @@ -16,4 +16,8 @@ * along with this program. If not, see . */ +/** + * General utility classes and methods that are independent of the + * specific domain (VM operator). + */ package org.jdrupes.vmoperator.util; \ No newline at end of file diff --git a/org.jdrupes.vmoperator.vmconlet/.checkstyle b/org.jdrupes.vmoperator.vmconlet/.checkstyle new file mode 100644 index 0000000..7f2c604 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/.checkstyle @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/org.jdrupes.vmoperator.vmconlet/.eclipse-pmd b/org.jdrupes.vmoperator.vmconlet/.eclipse-pmd new file mode 100644 index 0000000..8b394f8 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/.eclipse-pmd @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.jdrupes.vmoperator.vmconlet/.gitignore b/org.jdrupes.vmoperator.vmconlet/.gitignore new file mode 100644 index 0000000..a53e74c --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/.gitignore @@ -0,0 +1,4 @@ +/bin/ +/bin_test/ +/generated/ +/build/ diff --git a/org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.buildship.core.prefs b/org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..641c156 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,10 @@ +build.commands=org.eclipse.jdt.core.javabuilder +connection.arguments= +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) +connection.java.home=null +connection.jvm.arguments= +connection.project.dir=.. +derived.resources=.gradle,generated +eclipse.preferences.version=1 +natures=org.eclipse.jdt.groovy.core.groovyNature,org.eclipse.jdt.core.javanature +project.path=\:org.jgrapes.osgi.conlets.services diff --git a/org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.core.resources.prefs b/org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.core.runtime.prefs b/org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 0000000..5a0ad22 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +line.separator=\n diff --git a/org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.jdt.ui.prefs b/org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..784d01f --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,63 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_JGrapes +formatter_settings_version=13 +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=true +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=false +sp_cleanup.organize_imports=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=false +sp_cleanup.remove_trailing_whitespaces=false +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=true +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true +sp_jautodoc.cleanup.add_header=false +sp_jautodoc.cleanup.replace_header=false diff --git a/org.jdrupes.vmoperator.vmconlet/build.gradle b/org.jdrupes.vmoperator.vmconlet/build.gradle new file mode 100644 index 0000000..2031651 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/build.gradle @@ -0,0 +1,54 @@ +plugins { + id 'org.jdrupes.vmoperator.java-library-conventions' +} + +dependencies { + implementation project(':org.jdrupes.vmoperator.manager.events') + + implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.2.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.provider.vue:[1,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.provider.jgwcvuecomponents:[1,2)' +} + +apply plugin: 'com.github.node-gradle.node' + +node { + download = true +} + +task extractDependencies(type: Copy) { + from configurations.compileClasspath + .findAll{ it.name.contains('.provider.') + || it.name.contains('org.jgrapes.webconsole.base') + } + .collect{ zipTree (it) } + into 'build/unpacked' + duplicatesStrategy 'include' + } + +task compileTs(type: NodeTask) { + dependsOn ':npmInstall' + dependsOn extractDependencies + inputs.dir project.file('src') + inputs.file project.file('tsconfig.json') + inputs.file project.file('rollup.config.mjs') + outputs.dir project.file('build/generated/resources') + script = file("${rootProject.rootDir}/node_modules/rollup/dist/bin/rollup") + args = ["-c"] +} + +sourceSets { + main { + resources { + srcDir project.file('build/generated/resources') + } + } +} + +processResources { + dependsOn compileTs +} + +eclipse { + autoBuildTasks compileTs +} diff --git a/org.jdrupes.vmoperator.vmconlet/package.json b/org.jdrupes.vmoperator.vmconlet/package.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/package.json @@ -0,0 +1 @@ +{} diff --git a/org.jdrupes.vmoperator.vmconlet/resources/META-INF/services/org.jgrapes.webconsole.base.ConletComponentFactory b/org.jdrupes.vmoperator.vmconlet/resources/META-INF/services/org.jgrapes.webconsole.base.ConletComponentFactory new file mode 100644 index 0000000..5a22dc7 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/resources/META-INF/services/org.jgrapes.webconsole.base.ConletComponentFactory @@ -0,0 +1 @@ +org.jdrupes.vmoperator.vmconlet.VmConletFactory diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-l10nBundles.ftl.js b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-l10nBundles.ftl.js new file mode 100644 index 0000000..a4f1d50 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-l10nBundles.ftl.js @@ -0,0 +1,31 @@ +/* + * Moodle Tools Console + * Copyright (C) 2022 Michael N. Lipp + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU 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 General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see . + */ + +"use strict"; + +const l10nBundles = new Map(); +let entries = null; +// <#list supportedLanguages() as l> +entries = new Map(); +l10nBundles.set("${l.locale.toLanguageTag()}", entries); +// <#list l.l10nBundle.keys as key> +entries.set("${key}", "${l.l10nBundle.getString(key)}"); +// +// + +export default l10nBundles; diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-preview.ftl.html b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-preview.ftl.html new file mode 100644 index 0000000..7fa0a7f --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-preview.ftl.html @@ -0,0 +1,5 @@ +

diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html new file mode 100644 index 0000000..5e7d4b9 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html @@ -0,0 +1,84 @@ +
+ + + + + + + + + + + +
+ {{ localize(controller.label(key)) }} + + {{ localize("vmActions") }} +
+
diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n.properties b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n.properties new file mode 100644 index 0000000..d77bf1a --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n.properties @@ -0,0 +1,11 @@ +conletName = VM Viewer + +currentCpus = Current CPUs +currentRam = Current RAM +maximumCpus = Maximum CPUs +maximumRam = Maximum RAM +requestedCpus = Requested CPUs +requestedRam = Requested RAM +running = Running +vmActions = Actions +vmname = Name diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties new file mode 100644 index 0000000..5d8638a --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties @@ -0,0 +1,17 @@ +conletName = VM Anzeige + +running = Gestartet +currentCpus = Aktuelle CPUs +currentRam = Akuelles RAM +maximumCpus = Maximale CPUs +maximumRam = Maximales RAM +requestedCpus = Angeforderte CPUs +requestedRam = Angefordertes RAM +vmActions = Aktionen +vmname = Name + +Start\ VM = VM Starten +Stop\ VM = VM Anhalten + +Yes = Ja +No = Nein diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_en.properties b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_en.properties new file mode 100644 index 0000000..e69de29 diff --git a/org.jdrupes.vmoperator.vmconlet/rollup.config.mjs b/org.jdrupes.vmoperator.vmconlet/rollup.config.mjs new file mode 100644 index 0000000..7565030 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/rollup.config.mjs @@ -0,0 +1,35 @@ +import typescript from 'rollup-plugin-typescript2'; +import postcss from 'rollup-plugin-postcss'; + +let packagePath = "org/jdrupes/vmoperator/vmconlet"; +let baseName = "VmConlet" +let module = "build/generated/resources/" + packagePath + + "/" + baseName + "-functions.js"; + +let pathsMap = { + "aash-plugin": "../../page-resource/aash-vue-components/lib/aash-vue-components.js", + "jgconsole": "../../console-base-resource/jgconsole.js", + "jgwc": "../../page-resource/jgwc-vue-components/jgwc-components.js", + "l10nBundles": "./" + baseName + "-l10nBundles.ftl.js", + "vue": "../../page-resource/vue/vue.esm-browser.js" +} + +export default { + external: ['vue', 'aash-plugin', 'jgconsole', 'jgwc', 'l10nBundles'], + input: "src/" + packagePath + "/browser/" + baseName + "-functions.ts", + output: [ + { + format: "esm", + file: module, + sourcemap: true, + sourcemapPathTransform: (relativeSourcePath, _sourcemapPath) => { + return relativeSourcePath.replace(/^([^/]*\/){12}/, "./"); + }, + paths: pathsMap + } + ], + plugins: [ + typescript(), + postcss() + ] +}; diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java new file mode 100644 index 0000000..c9d5fde --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java @@ -0,0 +1,247 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.vmconlet; + +import com.google.gson.JsonObject; +import freemarker.core.ParseException; +import freemarker.template.MalformedTemplateNameException; +import freemarker.template.Template; +import freemarker.template.TemplateNotFoundException; +import io.kubernetes.client.custom.Quantity; +import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; +import java.io.IOException; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import org.jdrupes.json.JsonBeanDecoder; +import org.jdrupes.json.JsonDecodeException; +import org.jdrupes.vmoperator.manager.events.StartVm; +import org.jdrupes.vmoperator.manager.events.StopVm; +import org.jdrupes.vmoperator.manager.events.VmChannel; +import org.jdrupes.vmoperator.manager.events.VmDefChanged; +import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type; +import org.jdrupes.vmoperator.util.GsonPtr; +import org.jgrapes.core.Channel; +import org.jgrapes.core.Event; +import org.jgrapes.core.Manager; +import org.jgrapes.core.NamedChannel; +import org.jgrapes.core.annotation.Handler; +import org.jgrapes.webconsole.base.Conlet.RenderMode; +import org.jgrapes.webconsole.base.ConletBaseModel; +import org.jgrapes.webconsole.base.ConsoleConnection; +import org.jgrapes.webconsole.base.events.AddConletRequest; +import org.jgrapes.webconsole.base.events.AddConletType; +import org.jgrapes.webconsole.base.events.AddPageResources.ScriptResource; +import org.jgrapes.webconsole.base.events.ConsoleReady; +import org.jgrapes.webconsole.base.events.NotifyConletModel; +import org.jgrapes.webconsole.base.events.NotifyConletView; +import org.jgrapes.webconsole.base.events.RenderConlet; +import org.jgrapes.webconsole.base.events.RenderConletRequestBase; +import org.jgrapes.webconsole.base.events.SetLocale; +import org.jgrapes.webconsole.base.freemarker.FreeMarkerConlet; + +/** + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class VmConlet extends FreeMarkerConlet { + + private static final Set MODES = RenderMode.asSet( + RenderMode.Preview, RenderMode.View); + private final Map vmInfos + = new ConcurrentHashMap<>(); + + /** + * Creates a new component with its channel set to the given channel. + * + * @param componentChannel the channel that the component's handlers listen + * on by default and that {@link Manager#fire(Event, Channel...)} + * sends the event to + */ + public VmConlet(Channel componentChannel) { + super(componentChannel); + } + + /** + * On {@link ConsoleReady}, fire the {@link AddConletType}. + * + * @param event the event + * @param channel the channel + * @throws TemplateNotFoundException the template not found exception + * @throws MalformedTemplateNameException the malformed template name + * exception + * @throws ParseException the parse exception + * @throws IOException Signals that an I/O exception has occurred. + */ + @Handler + public void onConsoleReady(ConsoleReady event, ConsoleConnection channel) + throws TemplateNotFoundException, MalformedTemplateNameException, + ParseException, IOException { + // Add conlet resources to page + channel.respond(new AddConletType(type()) + .setDisplayNames( + localizations(channel.supportedLocales(), "conletName")) + .addRenderMode(RenderMode.Preview) + .addScript(new ScriptResource().setScriptType("module") + .setScriptUri(event.renderSupport().conletResource( + type(), "VmConlet-functions.js")))); + } + + @Override + protected Optional createNewState(AddConletRequest event, + ConsoleConnection connection, String conletId) throws Exception { + return Optional.of(new VmsModel(conletId)); + } + + @Override + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + protected Set doRenderConlet(RenderConletRequestBase event, + ConsoleConnection channel, String conletId, VmsModel conletState) + throws Exception { + Set renderedAs = new HashSet<>(); + boolean sendData = false; + if (event.renderAs().contains(RenderMode.Preview)) { + Template tpl + = freemarkerConfig().getTemplate("VmConlet-preview.ftl.html"); + channel.respond(new RenderConlet(type(), conletId, + processTemplate(event, tpl, + fmModel(event, channel, conletId, conletState))) + .setRenderAs( + RenderMode.Preview.addModifiers(event.renderAs())) + .setSupportedModes(MODES)); + renderedAs.add(RenderMode.View); + sendData = true; + } + if (event.renderAs().contains(RenderMode.View)) { + Template tpl + = freemarkerConfig().getTemplate("VmConlet-view.ftl.html"); + channel.respond(new RenderConlet(type(), conletId, + processTemplate(event, tpl, + fmModel(event, channel, conletId, conletState))) + .setRenderAs( + RenderMode.View.addModifiers(event.renderAs())) + .setSupportedModes(MODES)); + renderedAs.add(RenderMode.View); + sendData = true; + } + if (sendData) { + for (var vmInfo : vmInfos.values()) { + var def = JsonBeanDecoder.create(vmInfo.getRaw().toString()) + .readObject(); + channel.respond(new NotifyConletView(type(), + conletId, "updateVm", def)); + } + } + + return renderedAs; + } + + /** + * Track the VM definitions. + * + * @param event the event + * @param channel the channel + * @throws JsonDecodeException + */ + @Handler(namedChannels = "manager") + @SuppressWarnings({ "PMD.ConfusingTernary", + "PMD.AvoidInstantiatingObjectsInLoops" }) + public void onVmDefChanged(VmDefChanged event, VmChannel channel) + throws JsonDecodeException { + if (event.type() == Type.DELETED) { + vmInfos.remove(event.vmDefinition().getMetadata().getName()); + for (var entry : conletIdsByConsoleConnection().entrySet()) { + for (String conletId : entry.getValue()) { + entry.getKey().respond(new NotifyConletView(type(), + conletId, "removeVm")); + } + } + } else { + var vmDef = new DynamicKubernetesObject( + event.vmDefinition().getRaw().deepCopy()); + GsonPtr.to(vmDef.getRaw()).to("metadata").get(JsonObject.class) + .remove("managedFields"); + var vmSpec = GsonPtr.to(vmDef.getRaw()).to("spec", "vm"); + vmSpec.set("maximumRam", Quantity.fromString( + vmSpec.getAsString("maximumRam").orElse("0")).getNumber() + .toBigInteger().toString()); + vmSpec.set("currentRam", Quantity.fromString( + vmSpec.getAsString("currentRam").orElse("0")).getNumber() + .toBigInteger().toString()); + var status = GsonPtr.to(vmDef.getRaw()).to("status"); + status.set("ram", Quantity.fromString( + status.getAsString("ram").orElse("0")).getNumber() + .toBigInteger().toString()); + String vmName = event.vmDefinition().getMetadata().getName(); + vmInfos.put(vmName, vmDef); + + // Extract running + var def = JsonBeanDecoder.create(vmDef.getRaw().toString()) + .readObject(); + for (var entry : conletIdsByConsoleConnection().entrySet()) { + for (String conletId : entry.getValue()) { + entry.getKey().respond(new NotifyConletView(type(), + conletId, "updateVm", def)); + } + } + } + } + + @Override + protected void doUpdateConletState(NotifyConletModel event, + ConsoleConnection channel, VmsModel conletState) + throws Exception { + event.stop(); + switch (event.method()) { + case "start": + fire(new StartVm(event.params().asString(0), + new NamedChannel("manager"))); + break; + case "stop": + fire(new StopVm(event.params().asString(0), + new NamedChannel("manager"))); + break; + default:// ignore + break; + } + } + + @Override + protected boolean doSetLocale(SetLocale event, ConsoleConnection channel, + String conletId) throws Exception { + return true; + } + + /** + * The Class VmsModel. + */ + public class VmsModel extends ConletBaseModel { + + /** + * Instantiates a new vms model. + * + * @param conletId the conlet id + */ + public VmsModel(String conletId) { + super(conletId); + } + + } +} diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConletFactory.java b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConletFactory.java new file mode 100644 index 0000000..d77ceb6 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConletFactory.java @@ -0,0 +1,54 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.vmconlet; + +import java.util.Map; +import java.util.Optional; +import org.jgrapes.core.Channel; +import org.jgrapes.core.ComponentType; +import org.jgrapes.webconsole.base.ConletComponentFactory; + +/** + * The factory service for {@link VmConlet}s. + */ +public class VmConletFactory implements ConletComponentFactory { + + /* + * (non-Javadoc) + * + * @see org.jgrapes.core.ComponentFactory#componentType() + */ + @Override + public Class componentType() { + return VmConlet.class; + } + + /* + * (non-Javadoc) + * + * @see org.jgrapes.core.ComponentFactory#create(org.jgrapes.core.Channel, + * java.util.Map) + */ + @Override + public Optional create(Channel componentChannel, + Map properties) { + return Optional.of(new VmConlet(componentChannel)); + } + +} diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts new file mode 100644 index 0000000..f0f8919 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts @@ -0,0 +1,148 @@ +/* + * 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 . + */ + +import { reactive, ref, createApp, computed, onMounted } from "vue"; +import JGConsole from "jgconsole"; +import JgwcPlugin, { JGWC } from "jgwc"; +import { provideApi, getApi } from "aash-plugin"; +import l10nBundles from "l10nBundles"; + +import "./VmConlet-style.scss"; + +// +// Helpers +// +let unitMap = new Map(); +let unitMappings = new Array<{ key: string; value: bigint }>(); +let memorySize = /^\\s*(\\d+(\\.\\d+)?)\\s*([A-Za-z]*)\\s*/; + +// SI units and common abbreviations +let factor = BigInt("1"); +unitMap.set("", factor); +let scale = BigInt("1000"); +for (let unit of ["B", "kB", "MB", "GB", "TB", "PB", "EB"]) { + unitMap.set(unit, factor); + factor = factor * scale; +} +// Binary units +factor = BigInt("1024"); +scale = BigInt("1024"); +for (let unit of ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]) { + unitMap.set(unit, factor); + factor = factor * scale; +} +unitMap.forEach((value: bigint, key: string) => { + unitMappings.push({ key, value }); +}); +unitMappings.sort((a, b) => a.value < b.value ? 1 : a.value > b.value ? -1 : 0); + +function formatMemory(size: bigint): string { + for (let mapping of unitMappings) { + if (size >= mapping.value + && (size % mapping.value) === BigInt("0")) { + return (size / mapping.value + " " + mapping.key).trim(); + } + } + return size.toString(); +} + +// For global access +declare global { + interface Window { + orgJDrupesVmOperatorVmConlet: any; + } +} + +window.orgJDrupesVmOperatorVmConlet = {}; + +let vmInfos = reactive(new Map()); + +window.orgJDrupesVmOperatorVmConlet.initPreview + = (previewDom: HTMLElement, isUpdate: boolean) => { + const app = createApp({}); + app.use(JgwcPlugin, []); + app.config.globalProperties.window = window; + app.mount(previewDom); + }; + +window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, + isUpdate: boolean) => { + const app = createApp({ + setup(_props: any) { + const conletId: string + = (viewDom.parentNode!).dataset["conletId"]!; + + const localize = (key: string) => { + return JGConsole.localize( + l10nBundles, JGWC.lang() || "en", key); + }; + + const controller = reactive(new JGConsole.TableController([ + ["name", "vmname"], + ["running", "running"], + ["currentCpus", "currentCpus"], + ["currentRam", "currentRam"] + ], { + sortKey: "name", + sortOrder: "up" + })); + + let filteredData = computed(() => { + let infos = Array.from(vmInfos.values()); + return controller.filter(infos); + }); + + const vmAction = (vmName: string, action: string) => { + JGConsole.notifyConletModel(conletId, action, vmName); + }; + + const idScope = JGWC.createIdScope(); + const detailsByName = reactive(new Set()); + + return { + controller, vmInfos, filteredData, detailsByName, + localize, formatMemory, vmAction, + scopedId: (id: string) => { return idScope.scopedId(id); } + } + } + }); + app.use(JgwcPlugin); + app.config.globalProperties.window = window; + app.mount(viewDom); +}; + +JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", + "updateVm", function(conletId: String, vmDefinition: any) { + // Add some short-cuts for table controller + vmDefinition.name = vmDefinition.metadata.name; + vmDefinition.currentCpus = vmDefinition.status.cpus; + vmDefinition.currentRam = vmDefinition.status.ram; + for (let condition of vmDefinition.status.conditions) { + if (condition.type === "Running") { + vmDefinition.running = condition.status === "True"; + break; + } + } + + vmInfos.set(vmDefinition.name, vmDefinition); + }); + +JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", + "removeVm", function(conletId: String, vmName: String) { + vmInfos.delete(vmName); + }); diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss new file mode 100644 index 0000000..6d3168b --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss @@ -0,0 +1,51 @@ +/* + * 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 . + */ + +/* + * Conlet specific styles. + */ + +.jdrupes-vmoperator-vmconlet-view-search { + display: flex; + justify-content: flex-end +} + +.jdrupes-vmoperator-vmconlet-view-search form { + white-space: nowrap; +} + +.jdrupes-vmoperator-vmconlet-view-action-list { + white-space: nowrap; +} + +.jdrupes-vmoperator-vmconlet-view-action-list [role=button]:not(:last-child) { + margin-right: 0.5em; +} + +.jdrupes-vmoperator-vmconlet-view td { + vertical-align: top; +} + +.jdrupes-vmoperator-vmconlet-view td:not([colspan]):first-child { + white-space: nowrap; +} + +.jdrupes-vmoperator-vmconlet-view table td.details { + padding-left: 1em; +} + diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/l10nBundles-stub.d.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/l10nBundles-stub.d.ts new file mode 100644 index 0000000..8ca03f3 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/l10nBundles-stub.d.ts @@ -0,0 +1 @@ +export default new Map>(); diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/package-info.java b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/package-info.java new file mode 100644 index 0000000..2cbbfa7 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/package-info.java @@ -0,0 +1,19 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.vmconlet; \ No newline at end of file diff --git a/org.jdrupes.vmoperator.vmconlet/tsconfig.json b/org.jdrupes.vmoperator.vmconlet/tsconfig.json new file mode 100644 index 0000000..906e474 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "es2015", + "module": "es2015", + "sourceMap": true, + "inlineSources": true, + "declaration": true, + "importHelpers": true, + "strict": true, + "moduleResolution": "node", + "experimentalDecorators": true, + "lib": ["DOM", "ES2020"], + "paths": { + "aash-plugin": ["./build/unpacked/org/jgrapes/webconsole/provider/jgwcvuecomponents/aash-vue-components/lib/AashPlugin"], + "jgconsole": ["./build/unpacked/org/jgrapes/webconsole/base/JGConsole"], + "jgwc": ["./build/unpacked/org/jgrapes/webconsole/provider/jgwcvuecomponents/jgwc-vue-components/jgwc-components"], + "l10nBundles": ["./src/org/jdrupes/vmoperator/vmconlet/browser/l10nBundles-stub"], + "vue": ["./build/unpacked/org/jgrapes/webconsole/provider/vue/vue/vue"] + } + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "l10nBundles-stub.ts"] +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4eb8aaf --- /dev/null +++ b/package-lock.json @@ -0,0 +1,10226 @@ +{ + "name": "VM-Operator", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-replace": "^5.0.2", + "@rollup/plugin-terser": "^0.4.0", + "documentation": "^14.0.1", + "install": "^0.13.0", + "jsdoc": "^4.0.2", + "node-sass": "^9.0.0", + "npm": "^8.11.0", + "rollup": "^3.17.2", + "rollup-plugin-peer-deps-external": "^2.2.3", + "rollup-plugin-postcss": "^4.0.2", + "rollup-plugin-typescript2": "^0.36.0", + "rollup-plugin-vue": "^6.0.0", + "sass": "^1.49.9", + "terser": "^5.14.2", + "tslib": "^2.3.1", + "typedoc": "^0.25.1", + "typedoc-plugin-missing-exports": "^2.1.0", + "typescript": "^5.2.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", + "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsdoc/salty": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.5.tgz", + "integrity": "sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/fs/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/fs/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.3.tgz", + "integrity": "sha512-je7fu05B800IrMlWjb2wzJcdXzHYW46iTipfChnBDbIbDXhASZs27W1B58T2Yf45jZtJUONegpbce+9Ut2Ti/Q==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz", + "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.9.tgz", + "integrity": "sha512-8Hz50m2eoS56ldRlepxSBa6PWEVCtzUo/92HgLc2qTMnotJNIm7xP+UZhyWoYsyOdd5dxZ+NZLb24rsKyFs2ow==", + "dev": true, + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz", + "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==", + "dev": true + }, + "node_modules/@types/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-CqDQhn7jxaN9zw7zAu926zIx51ZzMaX8U8Wa4jGpKI6jeBr9ejFE68AQ+h+ztfrNJD+leo7K1cLbvMjpHfZSRg==", + "dev": true + }, + "node_modules/@types/hast": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.6.tgz", + "integrity": "sha512-47rJE80oqPmFdVDCD7IheXBrVdwuBgsYwoczFvKmwfo2Mzsnt+V9OONsYauFmICb6lQPpCuXYJWejBNs4pDJRg==", + "dev": true, + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-pTjcqY9E4nOI55Wgpz7eiI8+LzdYnw3qxXCfHyBDdPbYvbyLgWLJGh8EdPvqawwMK1Uo1794AUkkR38Fr0g+2g==", + "dev": true + }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "dev": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdast": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.13.tgz", + "integrity": "sha512-HjiGiWedR0DVFkeNljpa6Lv4/IZU1+30VY5d747K7lBudFc3R0Ibr6yJ9lN3BE28VnZyDfLF/VB1Ql1ZIbKrmg==", + "dev": true, + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.3.tgz", + "integrity": "sha512-T5k6kTXak79gwmIOaDF2UUQXFbnBE0zBUzF20pz7wDYu0RQMzWg+Ml/Pz50214NsFHBITkoi5VtdjFZnJ2ijjA==", + "dev": true + }, + "node_modules/@types/minimist": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.3.tgz", + "integrity": "sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==", + "dev": true + }, + "node_modules/@types/ms": { + "version": "0.7.32", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.32.tgz", + "integrity": "sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g==", + "dev": true + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz", + "integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==", + "dev": true + }, + "node_modules/@types/parse5": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-6.0.3.tgz", + "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", + "dev": true + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, + "node_modules/@types/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==", + "dev": true + }, + "node_modules/@types/unist": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.8.tgz", + "integrity": "sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==", + "dev": true + }, + "node_modules/@vue/compiler-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", + "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.21.3", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", + "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", + "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-ssr": "3.3.4", + "@vue/reactivity-transform": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0", + "postcss": "^8.1.10", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", + "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity-transform": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", + "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0" + } + }, + "node_modules/@vue/reactivity-transform/node_modules/magic-string": { + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@vue/shared": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", + "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==", + "dev": true + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dev": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001547", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz", + "integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "dev": true, + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", + "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "dev": true, + "dependencies": { + "cssnano-preset-default": "^5.2.14", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", + "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "dev": true, + "dependencies": { + "css-declaration-sorter": "^6.3.1", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.1", + "postcss-convert-values": "^5.1.3", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.4", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.4", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.2", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "optional": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dev": true, + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/doctrine-temporary-fork": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine-temporary-fork/-/doctrine-temporary-fork-2.1.0.tgz", + "integrity": "sha512-nliqOv5NkE4zMON4UA6AMJE6As35afs8aYXATpU4pTUdIKiARZwrJVEP1boA3Rx1ZXHVkwxkhcq4VkqvsuRLsA==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/documentation": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.2.tgz", + "integrity": "sha512-hWoTf8/u4pOjib02L7w94hwmhPfcSwyJNGtlPdGVe8GFyq8HkzcFzQQltaaikKunHEp0YSwDAbwBAO7nxrWIfA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.18.10", + "@babel/generator": "^7.18.10", + "@babel/parser": "^7.18.11", + "@babel/traverse": "^7.18.11", + "@babel/types": "^7.18.10", + "chalk": "^5.0.1", + "chokidar": "^3.5.3", + "diff": "^5.1.0", + "doctrine-temporary-fork": "2.1.0", + "git-url-parse": "^13.1.0", + "github-slugger": "1.4.0", + "glob": "^8.0.3", + "globals-docs": "^2.4.1", + "highlight.js": "^11.6.0", + "ini": "^3.0.0", + "js-yaml": "^4.1.0", + "konan": "^2.1.1", + "lodash": "^4.17.21", + "mdast-util-find-and-replace": "^2.2.1", + "mdast-util-inject": "^1.1.0", + "micromark-util-character": "^1.1.0", + "parse-filepath": "^1.0.2", + "pify": "^6.0.0", + "read-pkg-up": "^9.1.0", + "remark": "^14.0.2", + "remark-gfm": "^3.0.1", + "remark-html": "^15.0.1", + "remark-reference-links": "^6.0.1", + "remark-toc": "^8.0.1", + "resolve": "^1.22.1", + "strip-json-comments": "^5.0.0", + "unist-builder": "^3.0.0", + "unist-util-visit": "^4.1.0", + "vfile": "^5.3.4", + "vfile-reporter": "^7.0.4", + "vfile-sort": "^3.0.0", + "yargs": "^17.5.1" + }, + "bin": { + "documentation": "bin/documentation.js" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "@vue/compiler-sfc": "^3.2.37", + "vue-template-compiler": "^2.7.8" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.553", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.553.tgz", + "integrity": "sha512-HiRdtyKS2+VhiXvjhMvvxiMC33FJJqTA5EB2YHgFZW6v7HkK4Q9Ahv2V7O2ZPgAjw+MyCJVMQvigj13H8t+wvA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "dev": true, + "dependencies": { + "globule": "^1.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/generic-names": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-4.0.0.tgz", + "integrity": "sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==", + "dev": true, + "dependencies": { + "loader-utils": "^3.2.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/git-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz", + "integrity": "sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==", + "dev": true, + "dependencies": { + "is-ssh": "^1.4.0", + "parse-url": "^8.1.0" + } + }, + "node_modules/git-url-parse": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", + "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", + "dev": true, + "dependencies": { + "git-up": "^7.0.0" + } + }, + "node_modules/github-slugger": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz", + "integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ==", + "dev": true + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globals-docs": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/globals-docs/-/globals-docs-2.4.1.tgz", + "integrity": "sha512-qpPnUKkWnz8NESjrCvnlGklsgiQzlq+rcCxoG5uNQ+dNA7cFMCmn231slLAwS2N/PlkzZ3COL8CcS10jXmLHqg==", + "dev": true + }, + "node_modules/globule": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", + "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", + "dev": true, + "dependencies": { + "glob": "~7.1.1", + "lodash": "^4.17.21", + "minimatch": "~3.0.2" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/globule/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/globule/node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globule/node_modules/minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true + }, + "node_modules/hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "dev": true + }, + "node_modules/hast-util-from-parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", + "integrity": "sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "hastscript": "^7.0.0", + "property-information": "^6.0.0", + "vfile": "^5.0.0", + "vfile-location": "^4.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-7.2.3.tgz", + "integrity": "sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "@types/parse5": "^6.0.0", + "hast-util-from-parse5": "^7.0.0", + "hast-util-to-parse5": "^7.0.0", + "html-void-elements": "^2.0.0", + "parse5": "^6.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0", + "vfile": "^5.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-sanitize": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-4.1.0.tgz", + "integrity": "sha512-Hd9tU0ltknMGRDv+d6Ro/4XKzBqQnP/EZrpiTbpFYfXv/uOhWeKc+2uajcbEvAEH98VZd7eII2PiXm13RihnLw==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz", + "integrity": "sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "@types/unist": "^2.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-raw": "^7.0.0", + "hast-util-whitespace": "^2.0.0", + "html-void-elements": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz", + "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", + "integrity": "sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "optional": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/highlight.js": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz", + "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/html-void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz", + "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==", + "dev": true + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/immutable": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", + "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "dev": true + }, + "node_modules/import-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", + "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", + "dev": true, + "dependencies": { + "import-from": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/import-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", + "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-3.0.1.tgz", + "integrity": "sha512-it4HyVAUTKBc6m8e1iXWvXSTdndF7HbdN713+kvLrymxTaU4AUBWrJ4vEooP+V7fexnVD3LKcBshjGGPefSMUQ==", + "dev": true, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/install": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz", + "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-core-module": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-ssh": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", + "integrity": "sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==", + "dev": true, + "dependencies": { + "protocols": "^2.0.1" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-base64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", + "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", + "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdoc/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/konan": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/konan/-/konan-2.1.1.tgz", + "integrity": "sha512-7ZhYV84UzJ0PR/RJnnsMZcAbn+kLasJhVNWsu8ZyVEJYRpGA5XESQ9d/7zOa08U0Ou4cmB++hMNY/3OSV9KIbg==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.10.5", + "@babel/traverse": "^7.10.5" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, + "node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/markdown-table": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", + "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mdast-util-definitions": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.2.tgz", + "integrity": "sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.2.2.tgz", + "integrity": "sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz", + "integrity": "sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "mdast-util-to-string": "^3.1.0", + "micromark": "^3.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-decode-string": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "unist-util-stringify-position": "^3.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown/node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz", + "integrity": "sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==", + "dev": true, + "dependencies": { + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-gfm-autolink-literal": "^1.0.0", + "mdast-util-gfm-footnote": "^1.0.0", + "mdast-util-gfm-strikethrough": "^1.0.0", + "mdast-util-gfm-table": "^1.0.0", + "mdast-util-gfm-task-list-item": "^1.0.0", + "mdast-util-to-markdown": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-1.0.3.tgz", + "integrity": "sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "ccount": "^2.0.0", + "mdast-util-find-and-replace": "^2.0.0", + "micromark-util-character": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz", + "integrity": "sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0", + "micromark-util-normalize-identifier": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz", + "integrity": "sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz", + "integrity": "sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz", + "integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-inject": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-inject/-/mdast-util-inject-1.1.0.tgz", + "integrity": "sha512-CcJ0mHa36QYumDKiZ2OIR+ClhfOM7zIzN+Wfy8tRZ1hpH9DKLCS+Mh4DyK5bCxzE9uxMWcbIpeNFWsg1zrj/2g==", + "dev": true, + "dependencies": { + "mdast-util-to-string": "^1.0.0" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz", + "integrity": "sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==", + "dev": true, + "dependencies": { + "@types/hast": "^2.0.0", + "@types/mdast": "^3.0.0", + "mdast-util-definitions": "^5.0.0", + "micromark-util-sanitize-uri": "^1.1.0", + "trim-lines": "^3.0.0", + "unist-util-generated": "^2.0.0", + "unist-util-position": "^4.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz", + "integrity": "sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "@types/unist": "^2.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^3.0.0", + "mdast-util-to-string": "^3.0.0", + "micromark-util-decode-string": "^1.0.0", + "unist-util-visit": "^4.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", + "integrity": "sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-toc": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/mdast-util-toc/-/mdast-util-toc-6.1.1.tgz", + "integrity": "sha512-Er21728Kow8hehecK2GZtb7Ny3omcoPUVrmObiSUwmoRYVZaXLR751QROEFjR8W/vAQdHMLj49Lz20J55XaNpw==", + "dev": true, + "dependencies": { + "@types/extend": "^3.0.0", + "@types/mdast": "^3.0.0", + "extend": "^3.0.0", + "github-slugger": "^2.0.0", + "mdast-util-to-string": "^3.1.0", + "unist-util-is": "^5.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-toc/node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "dev": true + }, + "node_modules/mdast-util-toc/node_modules/mdast-util-to-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz", + "integrity": "sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true + }, + "node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dev": true, + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/meow/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/meow/node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/meow/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/micromark": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", + "integrity": "sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "micromark-core-commonmark": "^1.0.1", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.1.0.tgz", + "integrity": "sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-factory-destination": "^1.0.0", + "micromark-factory-label": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-factory-title": "^1.0.0", + "micromark-factory-whitespace": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-html-tag-name": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-subtokenize": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.1", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz", + "integrity": "sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==", + "dev": true, + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^1.0.0", + "micromark-extension-gfm-footnote": "^1.0.0", + "micromark-extension-gfm-strikethrough": "^1.0.0", + "micromark-extension-gfm-table": "^1.0.0", + "micromark-extension-gfm-tagfilter": "^1.0.0", + "micromark-extension-gfm-task-list-item": "^1.0.0", + "micromark-util-combine-extensions": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-1.0.5.tgz", + "integrity": "sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==", + "dev": true, + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz", + "integrity": "sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==", + "dev": true, + "dependencies": { + "micromark-core-commonmark": "^1.0.0", + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-normalize-identifier": "^1.0.0", + "micromark-util-sanitize-uri": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz", + "integrity": "sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==", + "dev": true, + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-classify-character": "^1.0.0", + "micromark-util-resolve-all": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz", + "integrity": "sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==", + "dev": true, + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz", + "integrity": "sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==", + "dev": true, + "dependencies": { + "micromark-util-types": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz", + "integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==", + "dev": true, + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz", + "integrity": "sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz", + "integrity": "sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz", + "integrity": "sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz", + "integrity": "sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz", + "integrity": "sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz", + "integrity": "sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz", + "integrity": "sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz", + "integrity": "sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz", + "integrity": "sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^1.0.0", + "micromark-util-decode-numeric-character-reference": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz", + "integrity": "sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz", + "integrity": "sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz", + "integrity": "sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz", + "integrity": "sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz", + "integrity": "sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-encode": "^1.0.0", + "micromark-util-symbol": "^1.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz", + "integrity": "sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^1.0.0", + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0", + "uvu": "^0.5.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nan": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", + "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/node-gyp/node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/node-gyp/node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/node-gyp/node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dev": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/node-gyp/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/node-gyp/node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "dev": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/node-gyp/node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/node-gyp/node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/node-gyp/node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/node-gyp/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/node-sass": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-9.0.0.tgz", + "integrity": "sha512-yltEuuLrfH6M7Pq2gAj5B6Zm7m+gdZoG66wTqG6mIZV/zijq3M2OO2HswtT6oBspPyFhHDcaxWpsBm0fRNDHPg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "async-foreach": "^0.1.3", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "lodash": "^4.17.15", + "make-fetch-happen": "^10.0.4", + "meow": "^9.0.0", + "nan": "^2.17.0", + "node-gyp": "^8.4.1", + "sass-graph": "^4.0.1", + "stdout-stream": "^1.4.0", + "true-case-path": "^2.2.1" + }, + "bin": { + "node-sass": "bin/node-sass" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/node-sass/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/node-sass/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/node-sass/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-sass/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-sass/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/node-sass/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-package-data/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/npm/-/npm-8.19.4.tgz", + "integrity": "sha512-3HANl8i9DKnUA89P4KEgVNN28EjSeDCmvEqbzOAuxCFDzdBZzjUl99zgnGpOUumvW5lvJo2HKcjrsc+tfyv1Hw==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/ci-detect", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/run-script", + "abbrev", + "archy", + "cacache", + "chalk", + "chownr", + "cli-columns", + "cli-table3", + "columnify", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "mkdirp", + "mkdirp-infer-owner", + "ms", + "node-gyp", + "nopt", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "npmlog", + "opener", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "read-package-json", + "read-package-json-fast", + "readdir-scoped-modules", + "rimraf", + "semver", + "ssri", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dev": true, + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^5.6.3", + "@npmcli/ci-detect": "^2.0.0", + "@npmcli/config": "^4.2.1", + "@npmcli/fs": "^2.1.0", + "@npmcli/map-workspaces": "^2.0.3", + "@npmcli/package-json": "^2.0.0", + "@npmcli/run-script": "^4.2.1", + "abbrev": "~1.1.1", + "archy": "~1.0.0", + "cacache": "^16.1.3", + "chalk": "^4.1.2", + "chownr": "^2.0.0", + "cli-columns": "^4.0.0", + "cli-table3": "^0.6.2", + "columnify": "^1.6.0", + "fastest-levenshtein": "^1.0.12", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "graceful-fs": "^4.2.10", + "hosted-git-info": "^5.2.1", + "ini": "^3.0.1", + "init-package-json": "^3.0.2", + "is-cidr": "^4.0.2", + "json-parse-even-better-errors": "^2.3.1", + "libnpmaccess": "^6.0.4", + "libnpmdiff": "^4.0.5", + "libnpmexec": "^4.0.14", + "libnpmfund": "^3.0.5", + "libnpmhook": "^8.0.4", + "libnpmorg": "^4.0.4", + "libnpmpack": "^4.1.3", + "libnpmpublish": "^6.0.5", + "libnpmsearch": "^5.0.4", + "libnpmteam": "^4.0.4", + "libnpmversion": "^3.0.7", + "make-fetch-happen": "^10.2.0", + "minimatch": "^5.1.0", + "minipass": "^3.1.6", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "ms": "^2.1.2", + "node-gyp": "^9.1.0", + "nopt": "^6.0.0", + "npm-audit-report": "^3.0.0", + "npm-install-checks": "^5.0.0", + "npm-package-arg": "^9.1.0", + "npm-pick-manifest": "^7.0.2", + "npm-profile": "^6.2.0", + "npm-registry-fetch": "^13.3.1", + "npm-user-validate": "^1.0.1", + "npmlog": "^6.0.2", + "opener": "^1.5.2", + "p-map": "^4.0.0", + "pacote": "^13.6.2", + "parse-conflict-json": "^2.0.2", + "proc-log": "^2.0.1", + "qrcode-terminal": "^0.12.0", + "read": "~1.0.7", + "read-package-json": "^5.0.2", + "read-package-json-fast": "^2.0.3", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^9.0.1", + "tar": "^6.1.11", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^2.0.0", + "validate-npm-package-name": "^4.0.0", + "which": "^2.0.2", + "write-file-atomic": "^4.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@colors/colors": { + "version": "1.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/npm/node_modules/@gar/promisify": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "5.6.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/map-workspaces": "^2.0.3", + "@npmcli/metavuln-calculator": "^3.0.1", + "@npmcli/move-file": "^2.0.0", + "@npmcli/name-from-folder": "^1.0.1", + "@npmcli/node-gyp": "^2.0.0", + "@npmcli/package-json": "^2.0.0", + "@npmcli/query": "^1.2.0", + "@npmcli/run-script": "^4.1.3", + "bin-links": "^3.0.3", + "cacache": "^16.1.3", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^5.2.1", + "json-parse-even-better-errors": "^2.3.1", + "json-stringify-nice": "^1.1.4", + "minimatch": "^5.1.0", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^6.0.0", + "npm-install-checks": "^5.0.0", + "npm-package-arg": "^9.0.0", + "npm-pick-manifest": "^7.0.2", + "npm-registry-fetch": "^13.0.0", + "npmlog": "^6.0.2", + "pacote": "^13.6.1", + "parse-conflict-json": "^2.0.1", + "proc-log": "^2.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^1.0.1", + "read-package-json-fast": "^2.0.2", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^9.0.0", + "treeverse": "^2.0.0", + "walk-up-path": "^1.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/ci-detect": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "4.2.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^2.0.2", + "ini": "^3.0.0", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^6.0.0", + "proc-log": "^2.0.0", + "read-package-json-fast": "^2.0.3", + "semver": "^7.3.5", + "walk-up-path": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ansi-styles": "^4.3.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^3.0.0", + "lru-cache": "^7.4.4", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^7.0.0", + "proc-log": "^2.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "1.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + }, + "bin": { + "installed-package-contents": "index.js" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents/node_modules/npm-bundled": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "2.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^1.0.1", + "glob": "^8.0.1", + "minimatch": "^5.0.1", + "read-package-json-fast": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^16.0.0", + "json-parse-even-better-errors": "^2.3.1", + "pacote": "^13.0.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/move-file": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "infer-owner": "^1.0.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "1.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^9.1.0", + "postcss-selector-parser": "^6.0.10", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "4.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^2.0.0", + "@npmcli/promise-spawn": "^3.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^2.0.3", + "which": "^2.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@tootallnate/once": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/agent-base": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/npm/node_modules/agentkeepalive": { + "version": "4.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/are-we-there-yet": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/asap": { + "version": "2.0.6", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^5.0.0", + "mkdirp-infer-owner": "^2.0.0", + "npm-normalize-package-bin": "^2.0.0", + "read-cmd-shim": "^3.0.0", + "rimraf": "^3.0.0", + "write-file-atomic": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/bin-links/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/builtins": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "16.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^4.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cli-table3": { + "version": "0.6.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/npm/node_modules/clone": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mkdirp-infer-owner": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/color-support": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/npm/node_modules/columnify": { + "version": "1.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/console-control-strings": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/debuglog": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/defaults": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/npm/node_modules/delegates": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/depd": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/dezalgo": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.1.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.12", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/function-bind": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/gauge": { + "version": "4.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "8.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.10", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/has": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/npm/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/has-unicode": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "5.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/humanize-ms": { + "version": "1.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^5.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/infer-owner": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/ini": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^9.0.1", + "promzard": "^0.3.0", + "read": "^1.0.7", + "read-package-json": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/ip": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^3.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/is-core-module": { + "version": "2.10.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-lambda": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "5.1.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.4.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "6.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "minipass": "^3.1.1", + "npm-package-arg": "^9.0.1", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "4.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/disparity-colors": "^2.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "binary-extensions": "^2.2.0", + "diff": "^5.1.0", + "minimatch": "^5.0.1", + "npm-package-arg": "^9.0.1", + "pacote": "^13.6.1", + "tar": "^6.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "4.0.14", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^5.6.3", + "@npmcli/ci-detect": "^2.0.0", + "@npmcli/fs": "^2.1.1", + "@npmcli/run-script": "^4.2.0", + "chalk": "^4.1.0", + "mkdirp-infer-owner": "^2.0.0", + "npm-package-arg": "^9.0.1", + "npmlog": "^6.0.2", + "pacote": "^13.6.1", + "proc-log": "^2.0.0", + "read": "^1.0.7", + "read-package-json-fast": "^2.0.2", + "semver": "^7.3.7", + "walk-up-path": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "3.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^5.6.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "8.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "4.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "4.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/run-script": "^4.1.3", + "npm-package-arg": "^9.0.1", + "pacote": "^13.6.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "6.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "normalize-package-data": "^4.0.0", + "npm-package-arg": "^9.0.1", + "npm-registry-fetch": "^13.0.0", + "semver": "^7.3.7", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "5.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "4.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "3.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^3.0.0", + "@npmcli/run-script": "^4.1.3", + "json-parse-even-better-errors": "^2.3.1", + "proc-log": "^2.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "7.13.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "10.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "5.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "3.3.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "2.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-json-stream": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/mkdirp-infer-owner": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "infer-owner": "^1.0.4", + "mkdirp": "^1.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "0.0.8", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/negotiator": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "9.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/nopt": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^5.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-bundled/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "9.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^5.0.0", + "proc-log": "^2.0.1", + "semver": "^7.3.5", + "validate-npm-package-name": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "5.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^8.0.1", + "ignore-walk": "^5.0.1", + "npm-bundled": "^2.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "bin": { + "npm-packlist": "bin/index.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-packlist/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^5.0.0", + "npm-normalize-package-bin": "^2.0.0", + "npm-package-arg": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "13.3.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "make-fetch-happen": "^10.0.6", + "minipass": "^3.1.6", + "minipass-fetch": "^2.0.3", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^9.0.1", + "proc-log": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/npmlog": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/once": { + "version": "1.4.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/opener": { + "version": "1.5.2", + "dev": true, + "inBundle": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/pacote": { + "version": "13.6.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^3.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/promise-spawn": "^3.0.0", + "@npmcli/run-script": "^4.1.0", + "cacache": "^16.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.6", + "mkdirp": "^1.0.4", + "npm-package-arg": "^9.0.0", + "npm-packlist": "^5.1.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^5.0.0", + "read-package-json-fast": "^2.0.3", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.1", + "just-diff": "^5.0.1", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "0.3.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "1" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "1.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json": { + "version": "5.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^8.0.1", + "json-parse-even-better-errors": "^2.3.1", + "normalize-package-data": "^4.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/read-package-json/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/readable-stream": { + "version": "3.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/readdir-scoped-modules": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm/node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/safe-buffer": { + "version": "5.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.3.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/set-blocking": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.7.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.3.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.11", + "dev": true, + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/ssri": { + "version": "9.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/string_decoder": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.1.11", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/wcwidth": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/npm/node_modules/which": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/wide-align": { + "version": "1.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/npm/node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "dev": true, + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-path": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", + "integrity": "sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==", + "dev": true, + "dependencies": { + "protocols": "^2.0.0" + } + }, + "node_modules/parse-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz", + "integrity": "sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==", + "dev": true, + "dependencies": { + "parse-path": "^7.0.0" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "dev": true, + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-6.1.0.tgz", + "integrity": "sha512-KocF8ve28eFjjuBKKGvzOBGzG8ew2OqOOSxTTZhirkzH7h3BI1vyzqlR0qbfcDBve1Yzo3FVlWUAtCRrbVN8Fw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", + "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", + "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "dev": true, + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.3.1.tgz", + "integrity": "sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==", + "dev": true, + "dependencies": { + "generic-names": "^4.0.0", + "icss-replace-symbols": "^1.1.0", + "lodash.camelcase": "^4.3.0", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "string-hash": "^1.1.1" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "dev": true, + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "dev": true, + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", + "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise.series": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", + "integrity": "sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/property-information": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.3.0.tgz", + "integrity": "sha512-gVNZ74nqhRMiIUYWGQdosYetaKc83x8oT41a0LlV3AAFCAZwCpg4vmGkq8t34+cUhp3cnM4XDiU/7xlgK7HGrg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/protocols": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", + "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==", + "dev": true + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/read-pkg": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", + "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^2.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", + "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", + "dev": true, + "dependencies": { + "find-up": "^6.3.0", + "read-pkg": "^7.1.0", + "type-fest": "^2.5.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/remark": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/remark/-/remark-14.0.3.tgz", + "integrity": "sha512-bfmJW1dmR2LvaMJuAnE88pZP9DktIFYXazkTfOIKZzi3Knk9lT0roItIA24ydOucI3bV/g/tXBA6hzqq3FV9Ew==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "remark-parse": "^10.0.0", + "remark-stringify": "^10.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-3.0.1.tgz", + "integrity": "sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-gfm": "^2.0.0", + "micromark-extension-gfm": "^2.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-html": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-15.0.2.tgz", + "integrity": "sha512-/CIOI7wzHJzsh48AiuIyIe1clxVkUtreul73zcCXLub0FmnevQE0UMFDQm7NUx8/3rl/4zCshlMfqBdWScQthw==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "hast-util-sanitize": "^4.0.0", + "hast-util-to-html": "^8.0.0", + "mdast-util-to-hast": "^12.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.2.tgz", + "integrity": "sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-from-markdown": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-reference-links": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/remark-reference-links/-/remark-reference-links-6.0.1.tgz", + "integrity": "sha512-34wY2C6HXSuKVTRtyJJwefkUD8zBOZOSHFZ4aSTnU2F656gr9WeuQ2dL6IJDK3NPd2F6xKF2t4XXcQY9MygAXg==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-10.0.3.tgz", + "integrity": "sha512-koyOzCMYoUHudypbj4XpnAKFbkddRMYZHwghnxd7ue5210WzGw6kOBwauJTRUMq16jsovXx8dYNvSSWP89kZ3A==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-to-markdown": "^1.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-toc": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/remark-toc/-/remark-toc-8.0.1.tgz", + "integrity": "sha512-7he2VOm/cy13zilnOTZcyAoyoolV26ULlon6XyCFU+vG54Z/LWJnwphj/xKIDLOt66QmJUgTyUvLVHi2aAElyg==", + "dev": true, + "dependencies": { + "@types/mdast": "^3.0.0", + "mdast-util-toc": "^6.0.0", + "unified": "^10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-peer-deps-external": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.4.tgz", + "integrity": "sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g==", + "dev": true, + "peerDependencies": { + "rollup": "*" + } + }, + "node_modules/rollup-plugin-postcss": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-4.0.2.tgz", + "integrity": "sha512-05EaY6zvZdmvPUDi3uCcAQoESDcYnv8ogJJQRp6V5kZ6J6P7uAVJlrTZcaaA20wTH527YTnKfkAoPxWI/jPp4w==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "concat-with-sourcemaps": "^1.1.0", + "cssnano": "^5.0.1", + "import-cwd": "^3.0.0", + "p-queue": "^6.6.2", + "pify": "^5.0.0", + "postcss-load-config": "^3.0.0", + "postcss-modules": "^4.0.0", + "promise.series": "^0.2.0", + "resolve": "^1.19.0", + "rollup-pluginutils": "^2.8.2", + "safe-identifier": "^0.4.2", + "style-inject": "^0.3.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "8.x" + } + }, + "node_modules/rollup-plugin-postcss/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/rollup-plugin-postcss/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup-plugin-postcss/node_modules/pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rollup-plugin-postcss/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup-plugin-typescript2": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.36.0.tgz", + "integrity": "sha512-NB2CSQDxSe9+Oe2ahZbf+B4bh7pHwjV5L+RSYpCu7Q5ROuN94F9b6ioWwKfz3ueL3KTtmX4o2MUH2cgHDIEUsw==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^4.1.2", + "find-cache-dir": "^3.3.2", + "fs-extra": "^10.0.0", + "semver": "^7.5.4", + "tslib": "^2.6.2" + }, + "peerDependencies": { + "rollup": ">=1.26.3", + "typescript": ">=2.4.0" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dev": true, + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/rollup-plugin-typescript2/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/rollup-plugin-vue": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-vue/-/rollup-plugin-vue-6.0.0.tgz", + "integrity": "sha512-oVvUd84d5u73M2HYM3XsMDLtZRIA/tw2U0dmHlXU2UWP5JARYHzh/U9vcxaN/x/9MrepY7VH3pHFeOhrWpxs/Q==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "hash-sum": "^2.0.0", + "rollup-pluginutils": "^2.8.2" + }, + "peerDependencies": { + "@vue/compiler-sfc": "*" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/rollup-pluginutils/node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-identifier": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz", + "integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==", + "dev": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "optional": true + }, + "node_modules/sass": { + "version": "1.69.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.3.tgz", + "integrity": "sha512-X99+a2iGdXkdWn1akFPs0ZmelUzyAQfvqYc2P/MPTrJRuIRoTffGzT9W9nFqG00S+c8hXzVmgxhUuHFdrwxkhQ==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-graph": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.1.tgz", + "integrity": "sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "lodash": "^4.17.11", + "scss-tokenizer": "^0.4.3", + "yargs": "^17.2.1" + }, + "bin": { + "sassgraph": "bin/sassgraph" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/sass-graph/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/sass-graph/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sass-graph/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/scss-tokenizer": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", + "integrity": "sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==", + "dev": true, + "dependencies": { + "js-base64": "^2.4.9", + "source-map": "^0.7.3" + } + }, + "node_modules/scss-tokenizer/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shiki": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", + "integrity": "sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==", + "dev": true, + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/smob": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz", + "integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==", + "dev": true + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", + "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "dev": true + }, + "node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", + "dev": true + }, + "node_modules/stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.1" + } + }, + "node_modules/stdout-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/stdout-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/stdout-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", + "integrity": "sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", + "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "dev": true, + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", + "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-inject": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz", + "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==", + "dev": true + }, + "node_modules/stylehacks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "dev": true, + "dependencies": { + "browserslist": "^4.21.4", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/supports-color": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", + "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/terser": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.21.0.tgz", + "integrity": "sha512-WtnFKrxu9kaoXuiZFSGrcAvvBqAdmKx0SFNmVNYdJamMu9yyN3I/QF0FbH4QcqJQ+y1CJnzxGIKH0cSj+FGYRw==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/trough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", + "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/true-case-path": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz", + "integrity": "sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==", + "dev": true + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedoc": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.2.tgz", + "integrity": "sha512-286F7BeATBiWe/qC4PCOCKlSTwfnsLbC/4cZ68oGBbvAqb9vV33quEOXx7q176OXotD+JdEerdQ1OZGJ818lnA==", + "dev": true, + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.3", + "shiki": "^0.14.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x" + } + }, + "node_modules/typedoc-plugin-missing-exports": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-2.1.0.tgz", + "integrity": "sha512-+1DhqZCEu7Vu5APnrqpPwl31D+hXpt1fV0Le9ycCRL1eLVdatdl6KVt4SEVwPxnEpKwgOn2dNX6I9+0F1aO2aA==", + "dev": true, + "peerDependencies": { + "typedoc": "0.24.x || 0.25.x" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "dev": true + }, + "node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/unist-builder": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.1.tgz", + "integrity": "sha512-gnpOw7DIpCA0vpr6NqdPvTWnlPTApCTRzr+38E6hCWx3rz/cjo83SsKIlS1Z+L5ttScQ2AwutNnb8+tAvpb6qQ==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-generated": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.1.tgz", + "integrity": "sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz", + "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "dev": true, + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz", + "integrity": "sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "dev": true, + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-reporter": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/vfile-reporter/-/vfile-reporter-7.0.5.tgz", + "integrity": "sha512-NdWWXkv6gcd7AZMvDomlQbK3MqFWL1RlGzMn++/O2TI+68+nqxCPTvLugdOtfSzXmjh+xUyhp07HhlrbJjT+mw==", + "dev": true, + "dependencies": { + "@types/supports-color": "^8.0.0", + "string-width": "^5.0.0", + "supports-color": "^9.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile": "^5.0.0", + "vfile-message": "^3.0.0", + "vfile-sort": "^3.0.0", + "vfile-statistics": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-reporter/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/vfile-reporter/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/vfile-reporter/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vfile-reporter/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/vfile-sort": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile-sort/-/vfile-sort-3.0.1.tgz", + "integrity": "sha512-1os1733XY6y0D5x0ugqSeaVJm9lYgj0j5qdcZQFyxlZOSy1jYarL77lLyb5gK4Wqr1d5OxmuyflSO3zKyFnTFw==", + "dev": true, + "dependencies": { + "vfile": "^5.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-statistics": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/vfile-statistics/-/vfile-statistics-2.0.1.tgz", + "integrity": "sha512-W6dkECZmP32EG/l+dp2jCLdYzmnDBIw6jwiLZSER81oR5AHRcVqL+k3Z+pfH1R73le6ayDkJRMk0sutj1bMVeg==", + "dev": true, + "dependencies": { + "vfile": "^5.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, + "node_modules/vue-template-compiler": { + "version": "2.7.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz", + "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==", + "dev": true, + "optional": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..42bf2fa --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "devDependencies": { + "@rollup/plugin-node-resolve": "^15.0.1", + "@rollup/plugin-replace": "^5.0.2", + "@rollup/plugin-terser": "^0.4.0", + "documentation": "^14.0.1", + "install": "^0.13.0", + "jsdoc": "^4.0.2", + "node-sass": "^9.0.0", + "npm": "^8.11.0", + "rollup": "^3.17.2", + "rollup-plugin-peer-deps-external": "^2.2.3", + "rollup-plugin-postcss": "^4.0.2", + "rollup-plugin-typescript2": "^0.36.0", + "rollup-plugin-vue": "^6.0.0", + "sass": "^1.49.9", + "terser": "^5.14.2", + "tslib": "^2.3.1", + "typedoc": "^0.25.1", + "typedoc-plugin-missing-exports": "^2.1.0", + "typescript": "^5.2.2" + }, + "eslintConfig": { + "root": true, + "extends": "eslint:recommended", + "rules": { + "quotes": "off" + } + }, + "eslintIgnore": [ + "node_modules/**" + ] +} diff --git a/settings.gradle b/settings.gradle index 9074ac6..cb613b6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,5 +11,8 @@ rootProject.name = 'VM-Operator' include 'org.jdrupes.vmoperator.manager' +include 'org.jdrupes.vmoperator.manager.events' +include 'org.jdrupes.vmoperator.vmconlet' include 'org.jdrupes.vmoperator.runner.qemu' +include 'org.jdrupes.vmoperator.common' include 'org.jdrupes.vmoperator.util' From c8b85b882da29f6a65ae5ba718198f2ad0baaec3 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:19:33 +0200 Subject: [PATCH 080/462] Feature/web gui (#13) Improve build and distribution. --- dev-example/kustomization.yaml | 5 ++++- org.jdrupes.vmoperator.manager/build.gradle | 22 +++++++++++++++---- .../vmoperator/manager/BasicTests.java | 13 +++++------ .../build.gradle | 16 ++++++++++++++ 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/dev-example/kustomization.yaml b/dev-example/kustomization.yaml index 142aff3..5bb4a62 100644 --- a/dev-example/kustomization.yaml +++ b/dev-example/kustomization.yaml @@ -43,7 +43,10 @@ patches: patch: |- - op: replace path: /spec/template/spec/containers/0/image - value: docker-registry.lan.mnl.de/vmoperator/org.jdrupes.vmoperator.manager:latest + value: docker-registry.lan.mnl.de/vmoperator/org.jdrupes.vmoperator.manager:test + - op: replace + path: /spec/template/spec/containers/0/imagePullPolicy + value: Always - op: replace path: /spec/replicas value: 0 diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index 902be49..c112737 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -57,6 +57,10 @@ task buildImage(type: Exec) { task tagLatestImage(type: Exec) { dependsOn buildImage + enabled = !project.version.contains("SNAPSHOT") + && !project.version.contains("alpha") \ + && !project.version.contains("beta") \ + commandLine 'podman', 'tag', "${project.name}:${project.version}",\ "${project.name}:latest" } @@ -78,20 +82,30 @@ task pushImage(type: Exec) { task pushLatestImage(type: Exec) { dependsOn buildLatestImage + enabled = !project.version.contains("SNAPSHOT") + && !project.version.contains("alpha") \ + && !project.version.contains("beta") \ + commandLine 'podman', 'push', '--tls-verify=false', \ "localhost/${project.name}:${project.version}", \ "${project.rootProject.properties['docker.registry']}" \ + "/${project.name}:latest" } -task pushForTest { - dependsOn pushImage - dependsOn pushLatestImage +task pushForTest(type: Exec) { + dependsOn buildImage + + commandLine 'podman', 'push', '--tls-verify=false', \ + "localhost/${project.name}:${project.version}", \ + "${project.rootProject.properties['docker.registry']}" \ + + "/${project.name}:test" } task pushImages { + // Don't push without testing first dependsOn test - dependsOn pushForTest + dependsOn pushImage + dependsOn pushLatestImage } test { diff --git a/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java b/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java index 532a25a..26eb387 100644 --- a/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java +++ b/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java @@ -1,21 +1,20 @@ package org.jdrupes.vmoperator.manager; -import static org.junit.jupiter.api.Assertions.*; - +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.kubernetes.client.dsl.base.ResourceDefinitionContext; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; import org.junit.jupiter.api.AfterAll; +import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.KubernetesClientBuilder; -import io.fabric8.kubernetes.client.dsl.base.ResourceDefinitionContext; -import io.fabric8.kubernetes.client.Config; class BasicTests { @@ -41,7 +40,7 @@ class BasicTests { .inNamespace("vmop-dev"); resourcesInNamespace.withName("unittest-vm").delete(); - // Update pod by scaling deployment + // Update manager pod by scaling deployment client.apps().deployments().inNamespace("vmop-dev") .withName("vm-operator").scale(0); client.apps().deployments().inNamespace("vmop-dev") diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle index 9221ba7..ec7de7f 100644 --- a/org.jdrupes.vmoperator.runner.qemu/build.gradle +++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle @@ -42,6 +42,10 @@ task buildArchImage(type: Exec) { task tagLatestArchImage(type: Exec) { dependsOn buildArchImage + enabled = !project.version.contains("SNAPSHOT") + && !project.version.contains("alpha") \ + && !project.version.contains("beta") \ + commandLine 'podman', 'tag', "${project.name}-arch:${project.version}",\ "${project.name}-arch:latest" } @@ -63,6 +67,10 @@ task pushArchImage(type: Exec) { task pushArchLatestImage(type: Exec) { dependsOn buildLatestArchImage + enabled = !project.version.contains("SNAPSHOT") + && !project.version.contains("alpha") \ + && !project.version.contains("beta") \ + commandLine 'podman', 'push', '--tls-verify=false', \ "localhost/${project.name}-arch:${project.version}", \ "${project.rootProject.properties['docker.registry']}" \ @@ -80,6 +88,10 @@ task buildAlpineImage(type: Exec) { task tagLatestAlpineImage(type: Exec) { dependsOn buildAlpineImage + enabled = !project.version.contains("SNAPSHOT") + && !project.version.contains("alpha") \ + && !project.version.contains("beta") \ + commandLine 'podman', 'tag', "${project.name}-alpine:${project.version}",\ "${project.name}-alpine:latest" } @@ -101,6 +113,10 @@ task pushAlpineImage(type: Exec) { task pushAlpineLatestImage(type: Exec) { dependsOn buildLatestAlpineImage + enabled = !project.version.contains("SNAPSHOT") + && !project.version.contains("alpha") \ + && !project.version.contains("beta") \ + commandLine 'podman', 'push', '--tls-verify=false', \ "localhost/${project.name}-alpine:${project.version}", \ "${project.rootProject.properties['docker.registry']}" \ From b8e1074150aee7fbc3d04a9dd8351209cff4319f Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:47:39 +0200 Subject: [PATCH 081/462] Feature/web gui (#14) --- .../src/org.jdrupes.vmoperator.versioning-conventions.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle index 21006a3..114db51 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle @@ -16,6 +16,9 @@ scmVersion { tag { def shortened = project.name.startsWith(project.group + ".") ? project.name.substring(project.group.length() + 1) : project.name + if (shortened == "manager") { + shortened = "manager-app"; + } var p = shortened.replace('.', '-') + "-" if (grgit.branch.current.name != "main" && !grgit.branch.current.name.startsWith("release")) { From 8567a2f052e8756338ea47fe3017f279a28ac432 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:23:13 +0200 Subject: [PATCH 082/462] Feature/web gui (#15) Add node display. --- .../jdrupes/vmoperator/common/Constants.java | 3 + org.jdrupes.vmoperator.manager/build.gradle | 4 +- .../jdrupes/vmoperator/manager/Constants.java | 3 - .../jdrupes/vmoperator/manager/VmWatcher.java | 56 +++++++++++++++---- .../vmconlet/VmConlet-view.ftl.html | 17 ++++-- .../vmoperator/vmconlet/l10n.properties | 1 + .../vmoperator/vmconlet/l10n_de.properties | 1 + .../jdrupes/vmoperator/vmconlet/VmConlet.java | 5 +- .../vmconlet/browser/VmConlet-functions.ts | 13 +++-- .../vmconlet/browser/VmConlet-style.scss | 15 +++++ 10 files changed, 90 insertions(+), 28 deletions(-) diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java index 22decd3..3ebe29d 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java @@ -23,6 +23,9 @@ package org.jdrupes.vmoperator.common; */ public class Constants { + /** The Constant APP_NAME. */ + public static final String APP_NAME = "vm-runner"; + /** The Constant VM_OP_NAME. */ public static final String VM_OP_NAME = "vm-operator"; diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index c112737..33a6fb5 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -19,7 +19,7 @@ dependencies { implementation 'org.jgrapes:org.jgrapes.util:[1.31.0,2)' implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.2.0,2)' - implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.3.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.5.0,2)' implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.0.0,2)' implementation 'org.jgrapes:org.jgrapes.webconlet.locallogin:[1.0.0,2)' @@ -39,7 +39,7 @@ dependencies { application { applicationName = 'vm-manager' - applicationDefaultJvmArgs = ['-Xmx50m', '-XX:+UseParallelGC', + applicationDefaultJvmArgs = ['-Xmx128m', '-XX:+UseParallelGC', '-Djava.util.logging.manager=org.jdrupes.vmoperator.util.LongLoggingManager' ] // Define the main class for the application. diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java index bf51a59..2ef4199 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java @@ -23,9 +23,6 @@ package org.jdrupes.vmoperator.manager; */ public class Constants extends org.jdrupes.vmoperator.common.Constants { - /** The Constant APP_NAME. */ - public static final String APP_NAME = "vm-runner"; - /** The Constant STATE_RUNNING. */ public static final String STATE_RUNNING = "Running"; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java index 492ad40..a09b4b2 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java @@ -18,6 +18,8 @@ package org.jdrupes.vmoperator.manager; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; @@ -31,6 +33,7 @@ import io.kubernetes.client.openapi.models.V1ObjectMeta; import io.kubernetes.client.util.Config; import io.kubernetes.client.util.Watch; 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.io.IOException; import java.nio.file.Files; @@ -52,6 +55,7 @@ import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type; +import org.jdrupes.vmoperator.util.GsonPtr; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.Components; @@ -251,8 +255,8 @@ public class VmWatcher extends Component { } private void handleVmDefinitionChange(V1APIResource vmsCrd, - Watch.Response item) { - V1ObjectMeta metadata = item.object.getMetadata(); + Watch.Response vmDefStub) { + V1ObjectMeta metadata = vmDefStub.object.getMetadata(); VmChannel channel = channels.computeIfAbsent(metadata.getName(), k -> { try { @@ -268,20 +272,52 @@ public class VmWatcher extends Component { return; } - // if (event.type() == Type.DELETED) { - // Get full definition and associate with channel as backup - var apiVersion = K8s.version(item.object.getApiVersion()); + var apiVersion = K8s.version(vmDefStub.object.getApiVersion()); DynamicKubernetesApi vmCrApi = new DynamicKubernetesApi(VM_OP_GROUP, apiVersion, vmsCrd.getName(), channel.client()); - var vmDef = K8s.get(vmCrApi, metadata); - vmDef.ifPresent(def -> channel.setVmDefinition(def)); + var curVmDef = K8s.get(vmCrApi, metadata); + curVmDef.ifPresent(def -> channel.setVmDefinition(def)); + + // Get eventual definition and augment with "dynamic" data. + var vmDef = curVmDef.orElse(channel.vmDefinition()); + addDynamicData(channel, vmDef); // Create and fire event channel.pipeline().fire(new VmDefChanged(VmDefChanged.Type - .valueOf(item.type), - channel.setGeneration(item.object.getMetadata().getGeneration()), - vmsCrd, vmDef.orElse(channel.vmDefinition())), channel); + .valueOf(vmDefStub.type), + channel + .setGeneration(vmDefStub.object.getMetadata().getGeneration()), + vmsCrd, vmDef), channel); + } + + private void addDynamicData(VmChannel channel, + DynamicKubernetesObject vmDef) { + var rootNode = GsonPtr.to(vmDef.getRaw()).get(JsonObject.class); + rootNode.addProperty("nodeName", ""); + + // VM definition status changes before the pod terminates. + // This results in pod information being shown for a stopped + // VM which is irritating. So check condition first. + var isRunning = GsonPtr.to(rootNode).to("status", "conditions") + .get(JsonArray.class) + .asList().stream().filter(el -> "Running" + .equals(((JsonObject) el).get("type").getAsString())) + .findFirst().map(el -> "True" + .equals(((JsonObject) el).get("status").getAsString())) + .orElse(false); + if (!isRunning) { + return; + } + var podSearch = new ListOptions(); + podSearch.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + + ",app.kubernetes.io/component=" + APP_NAME + + ",app.kubernetes.io/instance=" + vmDef.getMetadata().getName()); + var podList = K8s.podApi(channel.client()).list(namespaceToWatch, + podSearch); + podList.getObject().getItems().stream().forEach(pod -> { + rootNode.addProperty("nodeName", pod.getSpec().getNodeName()); + }); } /** diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html index 5e7d4b9..83492f4 100644 --- a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html @@ -33,26 +33,33 @@ - + + + v-html="formatMemory(entry[key])"> - - + + { public void onVmDefChanged(VmDefChanged event, VmChannel channel) throws JsonDecodeException { if (event.type() == Type.DELETED) { - vmInfos.remove(event.vmDefinition().getMetadata().getName()); + var vmName = event.vmDefinition().getMetadata().getName(); + vmInfos.remove(vmName); for (var entry : conletIdsByConsoleConnection().entrySet()) { for (String conletId : entry.getValue()) { entry.getKey().respond(new NotifyConletView(type(), - conletId, "removeVm")); + conletId, "removeVm", vmName)); } } } else { diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts index f0f8919..43ab44b 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts @@ -73,7 +73,7 @@ window.orgJDrupesVmOperatorVmConlet = {}; let vmInfos = reactive(new Map()); window.orgJDrupesVmOperatorVmConlet.initPreview - = (previewDom: HTMLElement, isUpdate: boolean) => { + = (previewDom: HTMLElement, _isUpdate: boolean) => { const app = createApp({}); app.use(JgwcPlugin, []); app.config.globalProperties.window = window; @@ -81,7 +81,7 @@ window.orgJDrupesVmOperatorVmConlet.initPreview }; window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, - isUpdate: boolean) => { + _isUpdate: boolean) => { const app = createApp({ setup(_props: any) { const conletId: string @@ -96,7 +96,8 @@ window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, ["name", "vmname"], ["running", "running"], ["currentCpus", "currentCpus"], - ["currentRam", "currentRam"] + ["currentRam", "currentRam"], + ["nodeName", "nodeName"] ], { sortKey: "name", sortOrder: "up" @@ -127,11 +128,11 @@ window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, }; JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", - "updateVm", function(conletId: String, vmDefinition: any) { + "updateVm", function(_conletId: String, vmDefinition: any) { // Add some short-cuts for table controller vmDefinition.name = vmDefinition.metadata.name; vmDefinition.currentCpus = vmDefinition.status.cpus; - vmDefinition.currentRam = vmDefinition.status.ram; + vmDefinition.currentRam = BigInt(vmDefinition.status.ram); for (let condition of vmDefinition.status.conditions) { if (condition.type === "Running") { vmDefinition.running = condition.status === "True"; @@ -143,6 +144,6 @@ JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", }); JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", - "removeVm", function(conletId: String, vmName: String) { + "removeVm", function(_conletId: String, vmName: String) { vmInfos.delete(vmName); }); diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss index 6d3168b..349d5be 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss @@ -49,3 +49,18 @@ padding-left: 1em; } +.jdrupes-vmoperator-vmconlet-view-table { + td.column-running { + text-align: center; + + span { + &.fa-check { + color: var(--success); + } + + &.fa-close { + color: var(--danger); + } + } + } +} From 6f45e7982a3992e9b8911ffa991ffb397f580bc3 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Mon, 30 Oct 2023 23:10:26 +0100 Subject: [PATCH 083/462] Feature/web gui2 (#16) Add oveview and enhance. --- .editorconfig | 2 +- deploy/kustomization.yaml | 1 + deploy/vmop-service.yaml | 12 + dev-example/config.yaml | 11 +- dev-example/kustomization.yaml | 31 +++ .../events/{StartVm.java => ModifyVm.java} | 31 ++- .../vmoperator/manager/events/StopVm.java | 52 ---- org.jdrupes.vmoperator.manager/build.gradle | 2 +- .../manager/console-footer.ftl.html | 3 + .../vmoperator/manager/AvoidEmptyPolicy.java | 94 +++++++ .../vmoperator/manager/Controller.java | 32 +-- .../jdrupes/vmoperator/manager/Manager.java | 1 + .../vmoperator/runner/qemu/Configuration.java | 9 +- .../vmoperator/runner/qemu/Runner.java | 5 +- .../org/jdrupes/vmoperator/util/GsonPtr.java | 31 ++- org.jdrupes.vmoperator.vmconlet/build.gradle | 5 +- .../vmconlet/VmConlet-preview.ftl.html | 45 +++- .../vmconlet/VmConlet-view.ftl.html | 28 ++- .../vmoperator/vmconlet/l10n.properties | 3 + .../vmoperator/vmconlet/l10n_de.properties | 11 + .../rollup.config.mjs | 3 +- .../vmoperator/vmconlet/TimeSeries.java | 144 +++++++++++ .../jdrupes/vmoperator/vmconlet/VmConlet.java | 209 +++++++++++++--- .../browser/ConditionalInputController.ts | 100 ++++++++ .../vmconlet/browser/CpuRamChart.ts | 138 ++++++++++ .../vmoperator/vmconlet/browser/MemorySize.ts | 65 +++++ .../vmoperator/vmconlet/browser/TimeSeries.ts | 91 +++++++ .../vmconlet/browser/VmConlet-functions.ts | 164 +++++++----- .../vmconlet/browser/VmConlet-style.scss | 68 +++-- org.jdrupes.vmoperator.vmconlet/tsconfig.json | 3 +- package-lock.json | 236 ++++++++++++++---- package.json | 2 +- 32 files changed, 1382 insertions(+), 250 deletions(-) create mode 100644 deploy/vmop-service.yaml rename org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/{StartVm.java => ModifyVm.java} (67%) delete mode 100644 org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/StopVm.java create mode 100644 org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-footer.ftl.html create mode 100644 org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/AvoidEmptyPolicy.java create mode 100644 org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java create mode 100644 org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/ConditionalInputController.ts create mode 100644 org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/CpuRamChart.ts create mode 100644 org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/MemorySize.ts create mode 100644 org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/TimeSeries.ts diff --git a/.editorconfig b/.editorconfig index 7e375e1..ad8e2c3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ charset = utf-8 insert_final_newline = true trim_trailing_whitespace = true -[*.{md,yml,yaml}] +[*.{html,md,yml,yaml}] indent_size = 2 indent_style = space diff --git a/deploy/kustomization.yaml b/deploy/kustomization.yaml index a988f88..bc9e17a 100644 --- a/deploy/kustomization.yaml +++ b/deploy/kustomization.yaml @@ -8,6 +8,7 @@ resources: - vmop-image-repository-pvc.yaml - vmop-config-map.yaml - vmop-deployment.yaml +- vmop-service.yaml - vmrunner-role.yaml - vmrunner-service-account.yaml - vmrunner-role-binding.yaml diff --git a/deploy/vmop-service.yaml b/deploy/vmop-service.yaml new file mode 100644 index 0000000..ea5cf66 --- /dev/null +++ b/deploy/vmop-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: vm-operator +spec: + ports: + - port: 8080 + protocol: TCP + targetPort: 8080 + selector: + app.kubernetes.io/name: vm-operator + app.kubernetes.io/component: manager diff --git a/dev-example/config.yaml b/dev-example/config.yaml index 26d53d4..4a471a8 100644 --- a/dev-example/config.yaml +++ b/dev-example/config.yaml @@ -23,15 +23,18 @@ password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2" "/RoleConfigurator": rolesByUser: + # User admin has role admin admin: - admin + # All users have role other "*": - - user + - other replace: false "/RoleConletFilter": conletTypesByRole: - user: - - "!org.jgrapes.webconlet.sysinfo.SysInfoConlet" - - "*" + # Admins can use all conlets admin: - "*" + # Others cannot use any conlet (except login conlet to log out) + other: + - org.jgrapes.webconlet.locallogin.LoginConlet diff --git a/dev-example/kustomization.yaml b/dev-example/kustomization.yaml index 5bb4a62..ae36fe1 100644 --- a/dev-example/kustomization.yaml +++ b/dev-example/kustomization.yaml @@ -34,6 +34,37 @@ patches: "/Reconciler": runnerData: storageClassName: null + "/GuiSocketServer": + port: 8888 + "/GuiHttpServer": + # This configures the GUI + "/ConsoleWeblet": + "/WebConsole": + "/LoginConlet": + users: + admin: + fullName: Administrator + password: "$2b$05$NiBd74ZGdplLC63ePZf1f.UtjMKkbQ23cQoO2OKOFalDBHWAOy21." + test: + fullName: Test Account + password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2" + "/RoleConfigurator": + rolesByUser: + # User admin has role admin + admin: + - admin + # All users have role other + "*": + - other + replace: false + "/RoleConletFilter": + conletTypesByRole: + # Admins can use all conlets + admin: + - "*" + # Others cannot use any conlet (except login conlet to log out) + other: + - org.jgrapes.webconlet.locallogin.LoginConlet - target: group: apps diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/StartVm.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ModifyVm.java similarity index 67% rename from org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/StartVm.java rename to org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ModifyVm.java index fb28f0a..8f735da 100644 --- a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/StartVm.java +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ModifyVm.java @@ -22,22 +22,27 @@ import org.jgrapes.core.Channel; import org.jgrapes.core.Event; /** - * Starts a VM. + * Modifies a VM. */ @SuppressWarnings("PMD.DataClass") -public class StartVm extends Event { +public class ModifyVm extends Event { private final String name; + private final String path; + private final Object value; /** - * Instantiates a new start vm event. + * Instantiates a new modify vm event. * * @param channels the channels * @param name the name */ - public StartVm(String name, Channel... channels) { + public ModifyVm(String name, String path, Object value, + Channel... channels) { super(channels); this.name = name; + this.path = path; + this.value = value; } /** @@ -49,4 +54,22 @@ public class StartVm extends Event { return name; } + /** + * Gets the path. + * + * @return the path + */ + public String path() { + return path; + } + + /** + * Gets the value. + * + * @return the value + */ + public Object value() { + return value; + } + } diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/StopVm.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/StopVm.java deleted file mode 100644 index a6d6281..0000000 --- a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/StopVm.java +++ /dev/null @@ -1,52 +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 . - */ - -package org.jdrupes.vmoperator.manager.events; - -import org.jgrapes.core.Channel; -import org.jgrapes.core.Event; - -/** - * Stops a VM. - */ -@SuppressWarnings("PMD.DataClass") -public class StopVm extends Event { - - private final String name; - - /** - * Instantiates a new start vm event. - * - * @param channels the channels - * @param name the name - */ - public StopVm(String name, Channel... channels) { - super(channels); - this.name = name; - } - - /** - * Gets the name. - * - * @return the name - */ - public String name() { - return name; - } - -} diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index 33a6fb5..faa87d1 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -39,7 +39,7 @@ dependencies { application { applicationName = 'vm-manager' - applicationDefaultJvmArgs = ['-Xmx128m', '-XX:+UseParallelGC', + applicationDefaultJvmArgs = ['-Xmx64m', '-XX:+UseParallelGC', '-Djava.util.logging.manager=org.jdrupes.vmoperator.util.LongLoggingManager' ] // Define the main class for the application. diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-footer.ftl.html b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-footer.ftl.html new file mode 100644 index 0000000..8147dca --- /dev/null +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-footer.ftl.html @@ -0,0 +1,3 @@ +
+Copyright © Michael N. Lipp 2023 +
diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/AvoidEmptyPolicy.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/AvoidEmptyPolicy.java new file mode 100644 index 0000000..000a21e --- /dev/null +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/AvoidEmptyPolicy.java @@ -0,0 +1,94 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.manager; + +import java.io.IOException; +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.webconsole.base.ConsoleConnection; +import org.jgrapes.webconsole.base.events.AddConletRequest; +import org.jgrapes.webconsole.base.events.ConsoleConfigured; +import org.jgrapes.webconsole.base.events.ConsoleReady; +import org.jgrapes.webconsole.base.events.RenderConlet; + +/** + * + */ +public class AvoidEmptyPolicy extends Component { + + private final String renderedFlagName = getClass().getName() + ".rendered"; + + /** + * Creates a new component with its channel set to the given channel. + * + * @param componentChannel + */ + public AvoidEmptyPolicy(Channel componentChannel) { + super(componentChannel); + } + + /** + * On console ready. + * + * @param event the event + * @param connection the connection + */ + @Handler + public void onConsoleReady(ConsoleReady event, + ConsoleConnection connection) { + connection.session().put(renderedFlagName, false); + } + + /** + * On render conlet. + * + * @param event the event + * @param connection the connection + */ + @Handler + public void onRenderConlet(RenderConlet event, + ConsoleConnection connection) { + connection.session().put(renderedFlagName, true); + } + + /** + * On console configured. + * + * @param event the event + * @param connection the console connection + * @throws InterruptedException the interrupted exception + */ + @Handler + public void onConsoleConfigured(ConsoleConfigured event, + ConsoleConnection connection) throws InterruptedException, + IOException { + if ((Boolean) connection.session().getOrDefault( + renderedFlagName, false)) { + return; + } + fire(new AddConletRequest(event.event().event().renderSupport(), + "org.jdrupes.vmoperator.vmconlet.VmConlet", + Conlet.RenderMode + .asSet(Conlet.RenderMode.Preview, Conlet.RenderMode.View)), + connection); + } + +} diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java index 10bb1b2..3f7badb 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java @@ -30,8 +30,7 @@ import java.util.logging.Level; import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM; import org.jdrupes.vmoperator.common.K8s; -import org.jdrupes.vmoperator.manager.events.StartVm; -import org.jdrupes.vmoperator.manager.events.StopVm; +import org.jdrupes.vmoperator.manager.events.ModifyVm; import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; @@ -152,30 +151,18 @@ public class Controller extends Component { } /** - * On start vm. + * On modify vm. * * @param event the event * @throws ApiException the api exception * @throws IOException Signals that an I/O exception has occurred. */ @Handler - public void onStartVm(StartVm event) throws ApiException, IOException { - patchRunning(event.name(), true); + public void onModigyVm(ModifyVm event) throws ApiException, IOException { + patchVmSpec(event.name(), event.path(), event.value()); } - /** - * On stop vm. - * - * @param event the event - * @throws ApiException the api exception - * @throws IOException Signals that an I/O exception has occurred. - */ - @Handler - public void onStopVm(StopVm event) throws ApiException, IOException { - patchRunning(event.name(), false); - } - - private void patchRunning(String name, boolean running) + private void patchVmSpec(String name, String path, Object value) throws ApiException, IOException { var crApi = K8s.crApi(Config.defaultClient(), VM_OP_GROUP, VM_OP_KIND_VM, namespace, name); @@ -188,12 +175,13 @@ public class Controller extends Component { // Patch running PatchOptions patchOpts = new PatchOptions(); patchOpts.setFieldManager("kubernetes-java-kubectl-apply"); + String valueAsText = value instanceof String + ? "\"" + value + "\"" + : value.toString(); var res = crApi.get().patch(namespace, name, V1Patch.PATCH_FORMAT_JSON_PATCH, - new V1Patch("[{\"op\": \"replace\", \"path\": " - + "\"/spec/vm/state\", " - + "\"value\": \"" + (running ? "Running" : "Stopped") - + "\"}]"), + new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/" + + path + "\", \"value\": " + valueAsText + "}]"), patchOpts); if (!res.isSuccess()) { logger.warning( diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java index f668c21..d39718f 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java @@ -135,6 +135,7 @@ public class Manager extends Component { console.attach(new BrowserLocalBackedKVStore( console.channel(), consoleWeblet.prefix().getPath())); console.attach(new KVStoreBasedConsolePolicy(console.channel())); + console.attach(new AvoidEmptyPolicy(console.channel())); console.attach(new RoleConfigurator(console.channel())); console.attach(new RoleConletFilter(console.channel())); console.attach(new LoginConlet(console.channel())); diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java index bbee56e..dda438b 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java @@ -28,6 +28,7 @@ import java.util.Set; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; +import static org.jdrupes.vmoperator.common.Constants.APP_NAME; import org.jdrupes.vmoperator.common.Convertions; import org.jdrupes.vmoperator.util.Dto; import org.jdrupes.vmoperator.util.FsdUtils; @@ -272,7 +273,7 @@ public class Configuration implements Dto { private boolean checkRuntimeDir() { // Runtime directory (sockets etc.) if (runtimeDir == null) { - var appDir = FsdUtils.runtimeDir(Runner.APP_NAME.replace("-", "")); + var appDir = FsdUtils.runtimeDir(APP_NAME.replace("-", "")); if (!Files.exists(appDir) && appDir.toFile().mkdirs()) { try { // When appDir is derived from XDG_RUNTIME_DIR @@ -288,7 +289,7 @@ public class Configuration implements Dto { runtimeDir)); } } - runtimeDir = FsdUtils.runtimeDir(Runner.APP_NAME.replace("-", "")) + runtimeDir = FsdUtils.runtimeDir(APP_NAME.replace("-", "")) .resolve(vm.name); runtimeDir.toFile().mkdir(); swtpmSocket = runtimeDir.resolve("swtpm-sock"); @@ -308,8 +309,8 @@ public class Configuration implements Dto { private boolean checkDataDir() { // Data directory if (dataDir == null) { - dataDir = FsdUtils.dataHome(Runner.APP_NAME.replace("-", "")) - .resolve(vm.name); + dataDir + = FsdUtils.dataHome(APP_NAME.replace("-", "")).resolve(vm.name); } if (!Files.exists(dataDir)) { dataDir.toFile().mkdirs(); diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index 3ace2e5..4e4ba18 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -50,6 +50,7 @@ import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import static org.jdrupes.vmoperator.common.Constants.APP_NAME; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCont; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; import org.jdrupes.vmoperator.runner.qemu.events.QmpConfigured; @@ -178,8 +179,6 @@ import org.jgrapes.util.events.WatchFile; "PMD.DataflowAnomalyAnalysis" }) public class Runner extends Component { - /** The Constant APP_NAME. */ - public static final String APP_NAME = "vm-runner"; private static final String TEMPLATE_DIR = "/opt/" + APP_NAME.replace("-", "") + "/templates"; private static final String DEFAULT_TEMPLATE @@ -609,7 +608,7 @@ public class Runner extends Component { static { try { InputStream props; - var path = FsdUtils.findConfigFile(Runner.APP_NAME.replace("-", ""), + var path = FsdUtils.findConfigFile(APP_NAME.replace("-", ""), "logging.properties"); if (path.isPresent()) { props = Files.newInputStream(path.get()); diff --git a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java index c16b3e0..e3d9fcd 100644 --- a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java +++ b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java @@ -23,6 +23,8 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import java.math.BigInteger; +import java.util.Collections; +import java.util.List; import java.util.Optional; import java.util.function.Supplier; @@ -30,7 +32,7 @@ import java.util.function.Supplier; * Utility class for pointing to elements on a Gson (Json) tree. */ @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", - "PMD.ClassWithOnlyPrivateConstructorsShouldBeFinal" }) + "PMD.ClassWithOnlyPrivateConstructorsShouldBeFinal", "PMD.GodClass" }) public class GsonPtr { private final JsonElement position; @@ -209,6 +211,21 @@ public class GsonPtr { .map(JsonPrimitive::getAsBoolean); } + /** + * Returns the elements of the selected {@link JsonArray} as list. + * + * @param the generic type + * @param cls the cls + * @param selectors the selectors + * @return the list + */ + @SuppressWarnings("unchecked") + public List getAsListOf(Class cls, + Object... selectors) { + return get(JsonArray.class, selectors).map(a -> (List) a.asList()) + .orElse(Collections.emptyList()); + } + /** * Sets the selected value. This pointer must point to a * {@link JsonObject} or {@link JsonArray}. The selector must @@ -248,6 +265,18 @@ public class GsonPtr { return set(selector, new JsonPrimitive(value)); } + /** + * Short for `set(selector, new JsonPrimitive(value))`. + * + * @param selector the selector + * @param value the value + * @return the gson ptr + * @see #set(Object, JsonElement) + */ + public GsonPtr set(Object selector, BigInteger value) { + return set(selector, new JsonPrimitive(value)); + } + /** * Same as {@link #set(Object, JsonElement)}, but sets the value * only if it doesn't exist yet, else returns the existing value. diff --git a/org.jdrupes.vmoperator.vmconlet/build.gradle b/org.jdrupes.vmoperator.vmconlet/build.gradle index 2031651..0adb40e 100644 --- a/org.jdrupes.vmoperator.vmconlet/build.gradle +++ b/org.jdrupes.vmoperator.vmconlet/build.gradle @@ -7,7 +7,9 @@ dependencies { implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.2.0,2)' implementation 'org.jgrapes:org.jgrapes.webconsole.provider.vue:[1,2)' - implementation 'org.jgrapes:org.jgrapes.webconsole.provider.jgwcvuecomponents:[1,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.provider.jgwcvuecomponents:[1.2,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.provider.chartjs:[1.2,2)' + } apply plugin: 'com.github.node-gradle.node' @@ -22,6 +24,7 @@ task extractDependencies(type: Copy) { || it.name.contains('org.jgrapes.webconsole.base') } .collect{ zipTree (it) } + exclude '*.class' into 'build/unpacked' duplicatesStrategy 'include' } diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-preview.ftl.html b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-preview.ftl.html index 7fa0a7f..11cd882 100644 --- a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-preview.ftl.html +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-preview.ftl.html @@ -1,5 +1,46 @@
-
Preview
+ data-jgwc-on-unload="JGConsole.jgwc.unmountVueApps"> + +
+
+ {{ localize("Period") }}: +
    +
  • + +
  • +
  • + +
  • +
+
+
+ + + + + + + + + + + + + + + + +
{{ localize("VMsSummary") }}:{{ vmSummary.runningVms }} / {{ vmSummary.totalVms }}
{{ localize("Used CPUs") }}:{{ vmSummary.usedCpus }}
{{ localize("Used RAM") }}:{{ formatMemory(Number(vmSummary.usedRam)) }}
+
+ +
+
diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html index 83492f4..0e4bb26 100644 --- a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html @@ -44,8 +44,10 @@ class="fa fa-check" :title="localize('Yes')"> + {{ shortDateTime(entry[key].toString()) }} + >{{ formatMemory(entry[key]) }} @@ -64,7 +66,7 @@ - + @@ -72,15 +74,31 @@ - + + - + - + +
{{ localize("maximumCpus") }}
{{ localize("requestedCpus") }}{{ entry.spec.vm.maximumCpus }}{{ entry.spec.vm.currentCpus }}
{{ cic.error }}
{{ localize("maximumRam") }}{{ formatMemory(BigInt(entry.spec.vm.maximumRam)) }}{{ formatMemory(Number(entry.spec.vm.maximumRam)) }}
{{ localize("requestedRam") }}{{ formatMemory(BigInt(entry.spec.vm.maximumRam)) }}{{ formatMemory(entry.spec.vm.currentRam) }}
{{ cic.error }}
diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n.properties b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n.properties index d0ee970..9a0efcf 100644 --- a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n.properties +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n.properties @@ -1,5 +1,8 @@ conletName = VM Viewer +VMsSummary = VMs (running/total) + +since = Since currentCpus = Current CPUs currentRam = Current RAM maximumCpus = Maximum CPUs diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties index bf19f93..d17bab6 100644 --- a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties @@ -1,6 +1,15 @@ conletName = VM Anzeige +VMsSummary = VMs (gestartet/gesamt) + +Used\ CPUs = Verwendete CPUs +Used\ RAM = Verwendetes RAM +Period = Zeitraum +Last\ hour = Letzte Stunde +Last\ day = Letzter Tag + running = Gestartet +since = Seit currentCpus = Aktuelle CPUs currentRam = Akuelles RAM maximumCpus = Maximale CPUs @@ -10,6 +19,8 @@ requestedCpus = Angeforderte CPUs requestedRam = Angefordertes RAM vmActions = Aktionen vmname = Name +Value\ is\ above\ maximum = Wert ist zu groß +Illegal\ format = Ungültiges Format Start\ VM = VM Starten Stop\ VM = VM Anhalten diff --git a/org.jdrupes.vmoperator.vmconlet/rollup.config.mjs b/org.jdrupes.vmoperator.vmconlet/rollup.config.mjs index 7565030..f1273ad 100644 --- a/org.jdrupes.vmoperator.vmconlet/rollup.config.mjs +++ b/org.jdrupes.vmoperator.vmconlet/rollup.config.mjs @@ -11,7 +11,8 @@ let pathsMap = { "jgconsole": "../../console-base-resource/jgconsole.js", "jgwc": "../../page-resource/jgwc-vue-components/jgwc-components.js", "l10nBundles": "./" + baseName + "-l10nBundles.ftl.js", - "vue": "../../page-resource/vue/vue.esm-browser.js" + "vue": "../../page-resource/vue/vue.esm-browser.js", + "chartjs": "../../page-resource/chart.js/auto.js" } export default { diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java new file mode 100644 index 0000000..cc17295 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java @@ -0,0 +1,144 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.vmconlet; + +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * The Class TimeSeries. + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class TimeSeries { + + private final List data = new LinkedList<>(); + private final Duration period; + + /** + * Instantiates a new time series. + * + * @param series the number of series + */ + public TimeSeries(Duration period) { + this.period = period; + } + + /** + * Adds data to the series. + * + * @param time the time + * @param numbers the numbers + * @return the time series + */ + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") + public TimeSeries add(Instant time, Number... numbers) { + var newEntry = new Entry(time, numbers); + boolean adjust = false; + if (data.size() >= 2) { + var lastEntry = data.get(data.size() - 1); + var lastButOneEntry = data.get(data.size() - 2); + adjust = lastEntry.valuesEqual(lastButOneEntry) + && lastEntry.valuesEqual(newEntry); + } + if (adjust) { + data.get(data.size() - 1).adjustTime(time); + } else { + data.add(new Entry(time, numbers)); + } + + // Purge + Instant limit = time.minus(period); + while (data.size() > 2 + && data.get(0).getTime().isBefore(limit) + && data.get(1).getTime().isBefore(limit)) { + data.remove(0); + } + return this; + } + + /** + * Returns the entries. + * + * @return the list + */ + public List entries() { + return data; + } + + /** + * The Class Entry. + */ + public static class Entry { + private Instant timestamp; + private final Number[] values; + + /** + * Instantiates a new entry. + * + * @param time the time + * @param numbers the numbers + */ + @SuppressWarnings("PMD.ArrayIsStoredDirectly") + public Entry(Instant time, Number... numbers) { + timestamp = time; + values = numbers; + } + + /** + * Changes the entry's time. + * + * @param time the time + */ + public void adjustTime(Instant time) { + timestamp = time; + } + + /** + * Returns the entry's time. + * + * @return the instant + */ + public Instant getTime() { + return timestamp; + } + + /** + * Returns the values. + * + * @return the number[] + */ + @SuppressWarnings("PMD.MethodReturnsInternalArray") + public Number[] getValues() { + return values; + } + + /** + * Returns `true` if both entries have the same values. + * + * @param other the other + * @return true, if successful + */ + public boolean valuesEqual(Entry other) { + return Arrays.equals(values, other.values); + } + } +} diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java index 86e152e..4f9c0a9 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java @@ -24,8 +24,13 @@ import freemarker.template.MalformedTemplateNameException; import freemarker.template.Template; import freemarker.template.TemplateNotFoundException; import io.kubernetes.client.custom.Quantity; +import io.kubernetes.client.custom.Quantity.Format; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Duration; +import java.time.Instant; import java.util.HashSet; import java.util.Map; import java.util.Optional; @@ -33,8 +38,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.jdrupes.json.JsonBeanDecoder; import org.jdrupes.json.JsonDecodeException; -import org.jdrupes.vmoperator.manager.events.StartVm; -import org.jdrupes.vmoperator.manager.events.StopVm; +import org.jdrupes.vmoperator.manager.events.ModifyVm; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type; @@ -59,6 +63,7 @@ import org.jgrapes.webconsole.base.events.SetLocale; import org.jgrapes.webconsole.base.freemarker.FreeMarkerConlet; /** + * The Class VmConlet. */ @SuppressWarnings("PMD.DataflowAnomalyAnalysis") public class VmConlet extends FreeMarkerConlet { @@ -67,6 +72,14 @@ public class VmConlet extends FreeMarkerConlet { RenderMode.Preview, RenderMode.View); private final Map vmInfos = new ConcurrentHashMap<>(); + private final TimeSeries summarySeries = new TimeSeries(Duration.ofDays(1)); + private Summary cachedSummary; + + /** + * The periodically generated update event. + */ + public static class Update extends Event { + } /** * Creates a new component with its channel set to the given channel. @@ -77,6 +90,7 @@ public class VmConlet extends FreeMarkerConlet { */ public VmConlet(Channel componentChannel) { super(componentChannel); + setPeriodicRefresh(Duration.ofMinutes(1), () -> new Update()); } /** @@ -116,7 +130,7 @@ public class VmConlet extends FreeMarkerConlet { ConsoleConnection channel, String conletId, VmsModel conletState) throws Exception { Set renderedAs = new HashSet<>(); - boolean sendData = false; + boolean sendVmInfos = false; if (event.renderAs().contains(RenderMode.Preview)) { Template tpl = freemarkerConfig().getTemplate("VmConlet-preview.ftl.html"); @@ -127,7 +141,12 @@ public class VmConlet extends FreeMarkerConlet { RenderMode.Preview.addModifiers(event.renderAs())) .setSupportedModes(MODES)); renderedAs.add(RenderMode.View); - sendData = true; + channel.respond(new NotifyConletView(type(), + conletId, "summarySeries", summarySeries.entries())); + var summary = evaluateSummary(false); + channel.respond(new NotifyConletView(type(), + conletId, "updateSummary", summary)); + sendVmInfos = true; } if (event.renderAs().contains(RenderMode.View)) { Template tpl @@ -139,9 +158,9 @@ public class VmConlet extends FreeMarkerConlet { RenderMode.View.addModifiers(event.renderAs())) .setSupportedModes(MODES)); renderedAs.add(RenderMode.View); - sendData = true; + sendVmInfos = true; } - if (sendData) { + if (sendVmInfos) { for (var vmInfo : vmInfos.values()) { var def = JsonBeanDecoder.create(vmInfo.getRaw().toString()) .readObject(); @@ -158,13 +177,14 @@ public class VmConlet extends FreeMarkerConlet { * * @param event the event * @param channel the channel - * @throws JsonDecodeException + * @throws JsonDecodeException the json decode exception + * @throws IOException */ @Handler(namedChannels = "manager") - @SuppressWarnings({ "PMD.ConfusingTernary", - "PMD.AvoidInstantiatingObjectsInLoops" }) + @SuppressWarnings({ "PMD.ConfusingTernary", "PMD.CognitiveComplexity", + "PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals" }) public void onVmDefChanged(VmDefChanged event, VmChannel channel) - throws JsonDecodeException { + throws JsonDecodeException, IOException { if (event.type() == Type.DELETED) { var vmName = event.vmDefinition().getMetadata().getName(); vmInfos.remove(vmName); @@ -175,25 +195,7 @@ public class VmConlet extends FreeMarkerConlet { } } } else { - var vmDef = new DynamicKubernetesObject( - event.vmDefinition().getRaw().deepCopy()); - GsonPtr.to(vmDef.getRaw()).to("metadata").get(JsonObject.class) - .remove("managedFields"); - var vmSpec = GsonPtr.to(vmDef.getRaw()).to("spec", "vm"); - vmSpec.set("maximumRam", Quantity.fromString( - vmSpec.getAsString("maximumRam").orElse("0")).getNumber() - .toBigInteger().toString()); - vmSpec.set("currentRam", Quantity.fromString( - vmSpec.getAsString("currentRam").orElse("0")).getNumber() - .toBigInteger().toString()); - var status = GsonPtr.to(vmDef.getRaw()).to("status"); - status.set("ram", Quantity.fromString( - status.getAsString("ram").orElse("0")).getNumber() - .toBigInteger().toString()); - String vmName = event.vmDefinition().getMetadata().getName(); - vmInfos.put(vmName, vmDef); - - // Extract running + var vmDef = prepareForSending(event); var def = JsonBeanDecoder.create(vmDef.getRaw().toString()) .readObject(); for (var entry : conletIdsByConsoleConnection().entrySet()) { @@ -203,20 +205,165 @@ public class VmConlet extends FreeMarkerConlet { } } } + + var summary = evaluateSummary(true); + summarySeries.add(Instant.now(), summary.usedCpus, summary.usedRam); + for (var entry : conletIdsByConsoleConnection().entrySet()) { + for (String conletId : entry.getValue()) { + entry.getKey().respond(new NotifyConletView(type(), + conletId, "updateSummary", summary)); + } + } + } + + @SuppressWarnings("PMD.AvoidDuplicateLiterals") + private DynamicKubernetesObject prepareForSending(VmDefChanged event) { + // Clone and remove managed fields + var vmDef = new DynamicKubernetesObject( + event.vmDefinition().getRaw().deepCopy()); + GsonPtr.to(vmDef.getRaw()).to("metadata").get(JsonObject.class) + .remove("managedFields"); + + // Convert RAM sizes to unitless numbers + var vmSpec = GsonPtr.to(vmDef.getRaw()).to("spec", "vm"); + vmSpec.set("maximumRam", Quantity.fromString( + vmSpec.getAsString("maximumRam").orElse("0")).getNumber() + .toBigInteger()); + vmSpec.set("currentRam", Quantity.fromString( + vmSpec.getAsString("currentRam").orElse("0")).getNumber() + .toBigInteger()); + var status = GsonPtr.to(vmDef.getRaw()).to("status"); + status.set("ram", Quantity.fromString( + status.getAsString("ram").orElse("0")).getNumber() + .toBigInteger()); + String vmName = event.vmDefinition().getMetadata().getName(); + vmInfos.put(vmName, vmDef); + return vmDef; + } + + /** + * Handle the periodic update event by sending {@link NotifyConletView} + * events. + * + * @param event the event + * @param connection the console connection + */ + @Handler + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + public void onUpdate(Update event, ConsoleConnection connection) { + var summary = evaluateSummary(false); + summarySeries.add(Instant.now(), summary.usedCpus, summary.usedRam); + for (String conletId : conletIds(connection)) { + connection.respond(new NotifyConletView(type(), + conletId, "updateSummary", summary)); + } + } + + /** + * The Class Summary. + */ + @SuppressWarnings("PMD.DataClass") + public static class Summary { + + /** The total vms. */ + public int totalVms; + + /** The running vms. */ + public int runningVms; + + /** The used cpus. */ + public int usedCpus; + + /** The used ram. */ + public BigInteger usedRam = BigInteger.ZERO; + + /** + * Gets the total vms. + * + * @return the totalVms + */ + public int getTotalVms() { + return totalVms; + } + + /** + * Gets the running vms. + * + * @return the runningVms + */ + public int getRunningVms() { + return runningVms; + } + + /** + * Gets the used cpus. + * + * @return the usedCpus + */ + public int getUsedCpus() { + return usedCpus; + } + + /** + * Gets the used ram. Returned as String for Json rendering. + * + * @return the usedRam + */ + public String getUsedRam() { + return usedRam.toString(); + } + + } + + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") + private Summary evaluateSummary(boolean force) { + if (!force && cachedSummary != null) { + return cachedSummary; + } + Summary summary = new Summary(); + for (var vmDef : vmInfos.values()) { + summary.totalVms += 1; + var status = GsonPtr.to(vmDef.getRaw()).to("status"); + summary.usedCpus += status.getAsInt("cpus").orElse(0); + summary.usedRam = summary.usedRam.add(status.getAsString("ram") + .map(BigInteger::new).orElse(BigInteger.ZERO)); + for (var c : status.getAsListOf(JsonObject.class, "conditions")) { + if ("Running".equals(GsonPtr.to(c).getAsString("type") + .orElse(null)) + && "True".equals(GsonPtr.to(c).getAsString("status") + .orElse(null))) { + summary.runningVms += 1; + } + } + } + cachedSummary = summary; + return summary; } @Override + @SuppressWarnings("PMD.AvoidDecimalLiteralsInBigDecimalConstructor") protected void doUpdateConletState(NotifyConletModel event, ConsoleConnection channel, VmsModel conletState) throws Exception { event.stop(); switch (event.method()) { case "start": - fire(new StartVm(event.params().asString(0), + fire(new ModifyVm(event.params().asString(0), "state", "Running", new NamedChannel("manager"))); break; case "stop": - fire(new StopVm(event.params().asString(0), + fire(new ModifyVm(event.params().asString(0), "state", "Stopped", + new NamedChannel("manager"))); + break; + case "cpus": + fire(new ModifyVm(event.params().asString(0), "currentCpus", + new BigDecimal(event.params().asDouble(1)).toBigInteger(), + new NamedChannel("manager"))); + break; + case "ram": + fire(new ModifyVm(event.params().asString(0), "currentRam", + new Quantity(new BigDecimal(event.params().asDouble(1)), + Format.BINARY_SI).toSuffixedString(), new NamedChannel("manager"))); break; default:// ignore diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/ConditionalInputController.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/ConditionalInputController.ts new file mode 100644 index 0000000..e06d245 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/ConditionalInputController.ts @@ -0,0 +1,100 @@ +/* + * 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 . + */ + +import { ref, Ref, nextTick } from "vue"; + +/** + * A controller for conditionally shown inputs. "Conditionally shown" + * means that the value is usually shown using some display element + * (e.g. `span`). Only when that elements gets the focus, it is replaced + * with an input element for editing the value. + */ +export default class ConditionlInputController { + + private submitCallback: (selected: string, value: any) => string | null; + private readonly inputKey = ref(""); + private startValue: any; + private inputElement: HTMLInputElement | null = null; + private errorMessage = ref(""); + + /** + * Creates a new controller. + */ + constructor(submitCallback: (selected: string, value: string) => string | null) { + // this.inputRef = inputRef; + this.submitCallback = submitCallback; + } + + get key() { + return this.inputKey.value; + } + + get error() { + return this.errorMessage.value; + } + + set input(element: HTMLInputElement) { + this.inputElement = element; + } + + startEdit (key: string, value: any) { + if (this.inputKey.value != "") { + return; + } + this.startValue = value; + this.errorMessage.value = ""; + this.inputKey.value = key; + nextTick(() => { + this.inputElement!.value = value; + this.inputElement!.focus(); + }); + } + + endEdit (converter?: (value: string) => any | null) : boolean { + if (typeof converter === 'undefined') { + this.inputKey.value = ""; + return false; + } + let newValue = converter(this.inputElement!.value); + if (newValue === this.startValue) { + this.inputKey.value = ""; + return false; + } + let submitResult = this.submitCallback (this.inputKey.value, newValue); + if (submitResult !== null) { + this.errorMessage.value = submitResult; + // Neither doing it directly nor doing it with nextTick works. + setTimeout(() => this.inputElement!.focus(), 10); + } else { + this.inputKey.value = ""; + } + + // In case it is called by form action + return false; + } + + get parseNumber() { + return (value: string): number | null => { + if (value.match(/^\d+$/)) { + return Number(value); + } + return null; + } + } + +} \ No newline at end of file diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/CpuRamChart.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/CpuRamChart.ts new file mode 100644 index 0000000..50525f0 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/CpuRamChart.ts @@ -0,0 +1,138 @@ +/* + * 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 . + */ + +import { Chart } from "chartjs"; +import TimeSeries from "./TimeSeries"; +import { formatMemory } from "./MemorySize"; +import JGConsole from "jgconsole"; +import l10nBundles from "l10nBundles"; +import { JGWC } from "jgwc"; + +export default class CpuRamChart extends Chart { + + private period = 24 * 3600 * 1000; + + constructor(canvas: HTMLCanvasElement, series: TimeSeries) { + super(canvas.getContext('2d')!, { + // The type of chart we want to create + type: 'line', + + // The data for our datasets + data: { + labels: series.getTimes(), + datasets: [{ + // See localize + data: series.getSeries(0), + yAxisID: 'cpus' + }, { + // See localize + data: series.getSeries(1), + yAxisID: 'ram' + }] + }, + + // Configuration options go here + options: { + animation: false, + maintainAspectRatio: false, + scales: { + x: { + type: 'time', + time: { minUnit: 'minute' }, + adapters: { + date: { + // See localize + } + } + }, + cpus: { + type: 'linear', + display: true, + position: 'left', + min: 0 + }, + ram: { + type: 'linear', + display: true, + position: 'right', + min: 0, + grid: { drawOnChartArea: false }, + ticks: { + stepSize: 1024 * 1024 * 1024, + callback: function(value, _index, _values) { + return formatMemory(Math.round(Number(value))); + } + } + } + } + } + }); + + let css = getComputedStyle(canvas); + this.setPropValue("options.plugins.legend.labels.font.family", css.fontFamily); + this.setPropValue("options.plugins.legend.labels.color", css.color); + this.setPropValue("options.scales.x.ticks.font.family", css.fontFamily); + this.setPropValue("options.scales.x.ticks.color", css.color); + this.setPropValue("options.scales.cpus.ticks.font.family", css.fontFamily); + this.setPropValue("options.scales.cpus.ticks.color", css.color); + this.setPropValue("options.scales.ram.ticks.font.family", css.fontFamily); + this.setPropValue("options.scales.ram.ticks.color", css.color); + + this.localizeChart(); + } + + setPeriod(period: number) { + this.period = period; + this.update(); + } + + setPropValue(path: string, value: any) { + let ptr: any = this; + let segs = path.split("."); + let lastSeg = segs.pop()!; + for (let seg of segs) { + let cur = ptr[seg]; + if (!cur) { + ptr[seg] = {}; + } + // ptr[seg] = ptr[seg] || {} + ptr = ptr[seg]; + } + ptr[lastSeg] = value; + } + + localizeChart() { + (this.options.scales?.x).adapters.date.locale = JGWC.lang(); + this.data.datasets[0].label + = JGConsole.localize(l10nBundles, JGWC.lang(), "Used CPUs") + this.data.datasets[1].label + = JGConsole.localize(l10nBundles, JGWC.lang(), "Used RAM") + this.update(); + } + + shift() { + this.setPropValue("options.scales.x.max", Date.now()); + this.update(); + } + + update() { + this.setPropValue("options.scales.x.min", Date.now() - this.period); + super.update(); + } +} + diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/MemorySize.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/MemorySize.ts new file mode 100644 index 0000000..d3795bf --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/MemorySize.ts @@ -0,0 +1,65 @@ +/* + * 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 . + */ + +let unitMap = new Map(); +let unitMappings = new Array<{ key: string; value: number }>(); +let memorySize = /^(\d+(\.\d+)?)\s*(B|kB|MB|GB|TB|PB|EB|KiB|MiB|GiB|TiB|PiB|EiB)?$/; + +// SI units and common abbreviations +let factor = 1; +unitMap.set("", factor); +let scale = 1000; +for (let unit of ["B", "kB", "MB", "GB", "TB", "PB", "EB"]) { + unitMap.set(unit, factor); + factor = factor * scale; +} + +// Binary units +factor = 1024; +scale = 1024; +for (let unit of ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]) { + unitMap.set(unit, factor); + factor = factor * scale; +} +unitMap.forEach((value: number, key: string) => { + unitMappings.push({ key, value }); +}); +unitMappings.sort((a, b) => a.value < b.value ? 1 : a.value > b.value ? -1 : 0); + +export function formatMemory(size: number): string { + for (let mapping of unitMappings) { + if (size >= mapping.value + && (size % mapping.value) === 0) { + return (size / mapping.value + " " + mapping.key).trim(); + } + } + return size.toString(); +} + +export function parseMemory(value: string): number | null { + let match = value.match(memorySize); + if (!match) { + return null; + } + + let unit = 1; + if (match[3]) { + unit = unitMap.get(match[3])!; + } + return Number(match[1]) * unit; +} diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/TimeSeries.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/TimeSeries.ts new file mode 100644 index 0000000..284663a --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/TimeSeries.ts @@ -0,0 +1,91 @@ +/* + * 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 . + */ + +type OnChangeCallback = ((ts: TimeSeries) => void) | null; + +export default class TimeSeries { + private timestamps: Date[] = []; + private series: number[][]; + private period: number; + private onChange: OnChangeCallback; + + constructor(nbOfSeries: number, period = 24 * 3600 * 1000, + onChange: OnChangeCallback = null) { + this.period = period; + this.onChange = onChange; + this.series = []; + while (this.series.length < nbOfSeries) { + this.series.push([]); + } + } + + clear() { + this.timestamps.length = 0; + for (let values of this.series) { + values.length = 0; + } + if (this.onChange) { + this.onChange(this); + } + } + + push(time: Date, ...values: number[]) { + let adjust = false; + if (this.timestamps.length >= 2) { + adjust = true; + for (let i = 0; i < values.length; i++) { + if (values[i] !== this.series[i][this.series[i].length - 1] + || values[i] !== this.series[i][this.series[i].length - 2]) { + adjust = false; + break; + } + } + } + if (adjust) { + this.timestamps[this.timestamps.length - 1] = time; + } else { + this.timestamps.push(time); + for (let i = 0; i < values.length; i++) { + this.series[i].push(values[i]); + } + } + + // Purge + let limit = time.getTime() - this.period; + while (this.timestamps.length > 2 + && this.timestamps[0].getTime() < limit + && this.timestamps[1].getTime() < limit) { + this.timestamps.shift(); + for (let values of this.series) { + values.shift(); + } + } + if (this.onChange) { + this.onChange(this); + } + } + + getTimes(): Date[] { + return this.timestamps; + } + + getSeries(n: number): number[] { + return this.series[n]; + } +} + diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts index 43ab44b..a2cbeec 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts @@ -16,51 +16,19 @@ * along with this program. If not, see . */ -import { reactive, ref, createApp, computed, onMounted } from "vue"; +import { + reactive, ref, Ref, createApp, computed, onMounted, watch, nextTick +} from "vue"; import JGConsole from "jgconsole"; import JgwcPlugin, { JGWC } from "jgwc"; -import { provideApi, getApi } from "aash-plugin"; import l10nBundles from "l10nBundles"; +import TimeSeries from "./TimeSeries"; +import { formatMemory, parseMemory } from "./MemorySize"; +import CpuRamChart from "./CpuRamChart"; +import ConditionlInputController from "./ConditionalInputController"; import "./VmConlet-style.scss"; -// -// Helpers -// -let unitMap = new Map(); -let unitMappings = new Array<{ key: string; value: bigint }>(); -let memorySize = /^\\s*(\\d+(\\.\\d+)?)\\s*([A-Za-z]*)\\s*/; - -// SI units and common abbreviations -let factor = BigInt("1"); -unitMap.set("", factor); -let scale = BigInt("1000"); -for (let unit of ["B", "kB", "MB", "GB", "TB", "PB", "EB"]) { - unitMap.set(unit, factor); - factor = factor * scale; -} -// Binary units -factor = BigInt("1024"); -scale = BigInt("1024"); -for (let unit of ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]) { - unitMap.set(unit, factor); - factor = factor * scale; -} -unitMap.forEach((value: bigint, key: string) => { - unitMappings.push({ key, value }); -}); -unitMappings.sort((a, b) => a.value < b.value ? 1 : a.value > b.value ? -1 : 0); - -function formatMemory(size: bigint): string { - for (let mapping of unitMappings) { - if (size >= mapping.value - && (size % mapping.value) === BigInt("0")) { - return (size / mapping.value + " " + mapping.key).trim(); - } - } - return size.toString(); -} - // For global access declare global { interface Window { @@ -71,14 +39,64 @@ declare global { window.orgJDrupesVmOperatorVmConlet = {}; let vmInfos = reactive(new Map()); +let vmSummary = reactive({ + totalVms: 0, + runningVms: 0, + usedCpus: 0, + usedRam: "" +}); -window.orgJDrupesVmOperatorVmConlet.initPreview - = (previewDom: HTMLElement, _isUpdate: boolean) => { - const app = createApp({}); - app.use(JgwcPlugin, []); - app.config.globalProperties.window = window; - app.mount(previewDom); - }; +const localize = (key: string) => { + return JGConsole.localize( + l10nBundles, JGWC.lang(), key); +}; + +const shortDateTime = (time: Date) => { + // https://stackoverflow.com/questions/63958875/why-do-i-get-rangeerror-date-value-is-not-finite-in-datetimeformat-format-w + return new Intl.DateTimeFormat(JGWC.lang(), + { dateStyle: "short", timeStyle: "short" }).format(new Date(time)); +}; + +// Cannot be reactive, leads to infinite recursion. +let chartData = new TimeSeries(2); +let chartDateUpdate = ref(null); + +window.orgJDrupesVmOperatorVmConlet.initPreview = (previewDom: HTMLElement, + _isUpdate: boolean) => { + const app = createApp({ + setup(_props: any) { + const conletId: string + = (previewDom.parentNode!).dataset["conletId"]!; + + let chart: CpuRamChart | null = null; + onMounted(() => { + let canvas: HTMLCanvasElement + = previewDom.querySelector(":scope .vmsChart")!; + chart = new CpuRamChart(canvas, chartData); + }) + + watch(chartDateUpdate, (_) => { + chart?.update(); + }) + + watch(JGWC.langRef(), (_) => { + chart?.localizeChart(); + }) + + const period: Ref = ref("day"); + + watch(period, (_) => { + let hours = (period.value === "day") ? 24 : 1; + chart?.setPeriod(hours * 3600 * 1000); + }); + + return { localize, formatMemory, vmSummary, period }; + } + }); + app.use(JgwcPlugin, []); + app.config.globalProperties.window = window; + app.mount(previewDom); +}; window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, _isUpdate: boolean) => { @@ -87,14 +105,10 @@ window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, const conletId: string = (viewDom.parentNode!).dataset["conletId"]!; - const localize = (key: string) => { - return JGConsole.localize( - l10nBundles, JGWC.lang() || "en", key); - }; - const controller = reactive(new JGConsole.TableController([ ["name", "vmname"], ["running", "running"], + ["runningConditionSince", "since"], ["currentCpus", "currentCpus"], ["currentRam", "currentRam"], ["nodeName", "nodeName"] @@ -114,12 +128,30 @@ window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, const idScope = JGWC.createIdScope(); const detailsByName = reactive(new Set()); + + const submitCallback = (selected: string, value: any) => { + if (value === null) { + return localize("Illegal format"); + } + let vmName = selected.substring(0, selected.lastIndexOf(":")); + let property = selected.substring(selected.lastIndexOf(":") + 1); + var vmDef = vmInfos.get(vmName); + let maxValue = vmDef.spec.vm["maximum" + + property.substring(0, 1).toUpperCase() + property.substring(1)]; + if (value > maxValue) { + return localize("Value is above maximum"); + } + JGConsole.notifyConletModel(conletId, property, vmName, value); + return null; + } + + const cic = new ConditionlInputController(submitCallback); return { - controller, vmInfos, filteredData, detailsByName, - localize, formatMemory, vmAction, + controller, vmInfos, filteredData, detailsByName, localize, + shortDateTime, formatMemory, vmAction, cic, parseMemory, scopedId: (id: string) => { return idScope.scopedId(id); } - } + }; } }); app.use(JgwcPlugin); @@ -132,14 +164,15 @@ JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", // Add some short-cuts for table controller vmDefinition.name = vmDefinition.metadata.name; vmDefinition.currentCpus = vmDefinition.status.cpus; - vmDefinition.currentRam = BigInt(vmDefinition.status.ram); + vmDefinition.currentRam = Number(vmDefinition.status.ram); for (let condition of vmDefinition.status.conditions) { if (condition.type === "Running") { vmDefinition.running = condition.status === "True"; + vmDefinition.runningConditionSince + = new Date(condition.lastTransitionTime); break; } } - vmInfos.set(vmDefinition.name, vmDefinition); }); @@ -147,3 +180,22 @@ JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", "removeVm", function(_conletId: String, vmName: String) { vmInfos.delete(vmName); }); + +JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", + "summarySeries", function(_conletId: String, series: any[]) { + chartData.clear(); + for (let entry of series) { + chartData.push(new Date(entry.time.epochSecond * 1000 + + entry.time.nano / 1000000), + entry.values[0], entry.values[1]); + } + chartDateUpdate.value = new Date(); +}); + +JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", + "updateSummary", function(_conletId: String, summary: any) { + chartData.push(new Date(), summary.usedCpus, Number(summary.usedRam)); + chartDateUpdate.value = new Date(); + Object.assign(vmSummary, summary); +}); + diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss index 349d5be..6ca0333 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss @@ -20,6 +20,23 @@ * Conlet specific styles. */ +.jdrupes-vmoperator-vmconlet-preview { + form { + float: right; + padding: 0.15em 0.3em; + border: 1px solid var(--panel-border); + border-radius: var(--corner-radius); + } + + table { + margin-bottom: 1em; + } + + .vmsChart-wrapper { + height: 12em; + } +} + .jdrupes-vmoperator-vmconlet-view-search { display: flex; justify-content: flex-end @@ -30,7 +47,7 @@ } .jdrupes-vmoperator-vmconlet-view-action-list { - white-space: nowrap; + white-space: nowrap; } .jdrupes-vmoperator-vmconlet-view-action-list [role=button]:not(:last-child) { @@ -39,28 +56,51 @@ .jdrupes-vmoperator-vmconlet-view td { vertical-align: top; + + &[tabindex] { + outline: 1px solid var(--primary); + cursor: text; + } } .jdrupes-vmoperator-vmconlet-view td:not([colspan]):first-child { - white-space: nowrap; + white-space: nowrap; } .jdrupes-vmoperator-vmconlet-view table td.details { - padding-left: 1em; + padding-left: 1em; } -.jdrupes-vmoperator-vmconlet-view-table { - td.column-running { - text-align: center; +.jdrupes-vmoperator-vmconlet-view-table { + td.column-running { + text-align: center; - span { - &.fa-check { - color: var(--success); - } + span { + &.fa-check { + color: var(--success); + } - &.fa-close { - color: var(--danger); - } - } + &.fa-close { + color: var(--danger); + } } + } + + td.details { + table { + + td:nth-child(2) { + min-width: 7em; + + input { + max-width: 5em; + } + } + + input~span { + margin-left: 0.5em; + color: var(--danger); + } + } + } } diff --git a/org.jdrupes.vmoperator.vmconlet/tsconfig.json b/org.jdrupes.vmoperator.vmconlet/tsconfig.json index 906e474..5fb2dd7 100644 --- a/org.jdrupes.vmoperator.vmconlet/tsconfig.json +++ b/org.jdrupes.vmoperator.vmconlet/tsconfig.json @@ -15,7 +15,8 @@ "jgconsole": ["./build/unpacked/org/jgrapes/webconsole/base/JGConsole"], "jgwc": ["./build/unpacked/org/jgrapes/webconsole/provider/jgwcvuecomponents/jgwc-vue-components/jgwc-components"], "l10nBundles": ["./src/org/jdrupes/vmoperator/vmconlet/browser/l10nBundles-stub"], - "vue": ["./build/unpacked/org/jgrapes/webconsole/provider/vue/vue/vue"] + "vue": ["./build/unpacked/org/jgrapes/webconsole/provider/vue/vue/vue"], + "chartjs": ["./build/unpacked/org/jgrapes/webconsole/provider/chartjs/chart.js/auto/auto"] } }, "include": ["src/**/*.ts"], diff --git a/package-lock.json b/package-lock.json index 4eb8aaf..1a31645 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "jsdoc": "^4.0.2", "node-sass": "^9.0.0", "npm": "^8.11.0", - "rollup": "^3.17.2", + "rollup": "^4.1.5", "rollup-plugin-peer-deps-external": "^2.2.3", "rollup-plugin-postcss": "^4.0.2", "rollup-plugin-typescript2": "^0.36.0", @@ -613,13 +613,13 @@ } }, "node_modules/@rollup/plugin-replace": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.3.tgz", - "integrity": "sha512-je7fu05B800IrMlWjb2wzJcdXzHYW46iTipfChnBDbIbDXhASZs27W1B58T2Yf45jZtJUONegpbce+9Ut2Ti/Q==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.5.tgz", + "integrity": "sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", - "magic-string": "^0.27.0" + "magic-string": "^0.30.3" }, "engines": { "node": ">=14.0.0" @@ -677,6 +677,162 @@ } } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.1.5.tgz", + "integrity": "sha512-/fwx6GS8cIbM2rTNyLMxjSCOegHywOdXO+kN9yFy018iCULcKZCyA3xvzw4bxyKbYfdSxQgdhbsl0egNcxerQw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.1.5.tgz", + "integrity": "sha512-tmXh7dyEt+JEz/NgDJlB1UeL/1gFV0v8qYzUAU42WZH4lmUJ5rp6/HkR2qUNC5jCgYEwd8/EfbHKtGIEfS4CUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.1.5.tgz", + "integrity": "sha512-lTDmLxdEVhzI3KCesZUrNbl3icBvPrDv/85JasY5gh4P2eAuDFmM4uj9HC5DdH0anLC0fwJ+1Uzasr4qOXcjRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.1.5.tgz", + "integrity": "sha512-v6qEHZyjWnIgcc4oiy8AIeFsUJAx+Kg0sLj+RE7ICwv3u7YC/+bSClxAiBASRjMzqsq0Z+I/pfxj+OD8mjBYxg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.1.5.tgz", + "integrity": "sha512-WngCfwPEDUNbZR1FNO2TCROYUwJvRlbvPi3AS85bDUkkoRDBcjUIz42cuB1j4PKilmnZascL5xTMF/yU8YFayA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.1.5.tgz", + "integrity": "sha512-Q2A/PEP/UTPTOBwgar3mmCaApahoezai/8e/7f4GCLV6XWCpnU4YwkQQtla7d7nUnc792Ps7g1G0WMovzIknrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.1.5.tgz", + "integrity": "sha512-84aBKNAVzTU/eG3tb2+kR4NGRAtm2YVW/KHwkGGDR4z1k4hyrDbuImsfs/6J74t6y0YLOe9HOSu7ejRjzUBGVQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.1.5.tgz", + "integrity": "sha512-mldtP9UEBurIq2+GYMdNeiqCLW1fdgf4KdkMR/QegAeXk4jFHkKQl7p0NITrKFVyVqzISGXH5gR6GSTBH4wszw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.1.5.tgz", + "integrity": "sha512-36p+nMcSxjAEzfU47+by102HolUtf/EfgBAidocTKAofJMTqG5QD50qzaFLk4QO+z7Qvg4qd0wr99jGAwnKOig==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.1.5.tgz", + "integrity": "sha512-5oxhubo0A3J8aF/tG+6jHBg785HF8/88kl1YnfbDKmnqMxz/EFiAQDH9cq6lbnxofjn8tlq5KiTf0crJGOGThg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.1.5.tgz", + "integrity": "sha512-uVQyBREKX9ErofL8KAZ4iVlqzSZOXSIG+BOLYuz5FD+Cg6jh1eLIeUa3Q4SgX0QaTRFeeAgSNqCC+8kZrZBpSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.1.5.tgz", + "integrity": "sha512-FQ5qYqRJ2vUBSom3Fos8o/6UvAMOvlus4+HGCAifH1TagbbwVnVVe0o01J1V52EWnQ8kmfpJDJ0FMrfM5yzcSA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -838,18 +994,6 @@ "source-map-js": "^1.0.2" } }, - "node_modules/@vue/compiler-sfc/node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@vue/compiler-ssr": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", @@ -873,18 +1017,6 @@ "magic-string": "^0.30.0" } }, - "node_modules/@vue/reactivity-transform/node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", - "dev": true, - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@vue/shared": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", @@ -3093,12 +3225,12 @@ "dev": true }, "node_modules/magic-string": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", - "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" + "@jridgewell/sourcemap-codec": "^1.4.15" }, "engines": { "node": ">=12" @@ -8770,18 +8902,30 @@ } }, "node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.1.5.tgz", + "integrity": "sha512-AEw14/q4NHYQkQlngoSae2yi7hDBeT9w84aEzdgCr39+2RL+iTG84lGTkgC1Wp5igtquN64cNzuzZKVz+U6jOg==", "dev": true, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14.18.0", + "node": ">=18.0.0", "npm": ">=8.0.0" }, "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.1.5", + "@rollup/rollup-android-arm64": "4.1.5", + "@rollup/rollup-darwin-arm64": "4.1.5", + "@rollup/rollup-darwin-x64": "4.1.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.1.5", + "@rollup/rollup-linux-arm64-gnu": "4.1.5", + "@rollup/rollup-linux-arm64-musl": "4.1.5", + "@rollup/rollup-linux-x64-gnu": "4.1.5", + "@rollup/rollup-linux-x64-musl": "4.1.5", + "@rollup/rollup-win32-arm64-msvc": "4.1.5", + "@rollup/rollup-win32-ia32-msvc": "4.1.5", + "@rollup/rollup-win32-x64-msvc": "4.1.5", "fsevents": "~2.3.2" } }, @@ -9008,9 +9152,9 @@ "optional": true }, "node_modules/sass": { - "version": "1.69.3", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.3.tgz", - "integrity": "sha512-X99+a2iGdXkdWn1akFPs0ZmelUzyAQfvqYc2P/MPTrJRuIRoTffGzT9W9nFqG00S+c8hXzVmgxhUuHFdrwxkhQ==", + "version": "1.69.5", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", + "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -9517,9 +9661,9 @@ "dev": true }, "node_modules/terser": { - "version": "5.21.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.21.0.tgz", - "integrity": "sha512-WtnFKrxu9kaoXuiZFSGrcAvvBqAdmKx0SFNmVNYdJamMu9yyN3I/QF0FbH4QcqJQ+y1CJnzxGIKH0cSj+FGYRw==", + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.23.0.tgz", + "integrity": "sha512-Iyy83LN0uX9ZZLCX4Qbu5JiHiWjOCTwrmM9InWOzVeM++KNWEsqV4YgN9U9E8AlohQ6Gs42ztczlWOG/lwDAMA==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -9615,9 +9759,9 @@ } }, "node_modules/typedoc": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.2.tgz", - "integrity": "sha512-286F7BeATBiWe/qC4PCOCKlSTwfnsLbC/4cZ68oGBbvAqb9vV33quEOXx7q176OXotD+JdEerdQ1OZGJ818lnA==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.3.tgz", + "integrity": "sha512-Ow8Bo7uY1Lwy7GTmphRIMEo6IOZ+yYUyrc8n5KXIZg1svpqhZSWgni2ZrDhe+wLosFS8yswowUzljTAV/3jmWw==", "dev": true, "dependencies": { "lunr": "^2.3.9", diff --git a/package.json b/package.json index 42bf2fa..1e01398 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "jsdoc": "^4.0.2", "node-sass": "^9.0.0", "npm": "^8.11.0", - "rollup": "^3.17.2", + "rollup": "^4.1.5", "rollup-plugin-peer-deps-external": "^2.2.3", "rollup-plugin-postcss": "^4.0.2", "rollup-plugin-typescript2": "^0.36.0", From 39a2a0389e1f3290dcccc5edf0617f2f3cee6f43 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Tue, 31 Oct 2023 23:03:48 +0100 Subject: [PATCH 084/462] Bugfix/ts style (#17) Fix style warnings. --- .eslintrc.json | 16 + .../.eslintrc.json | 15 + .../browser/ConditionalInputController.ts | 18 +- .../vmconlet/browser/CpuRamChart.ts | 12 +- .../vmoperator/vmconlet/browser/MemorySize.ts | 14 +- .../vmoperator/vmconlet/browser/TimeSeries.ts | 6 +- .../vmconlet/browser/VmConlet-functions.ts | 55 +- .../vmconlet/browser/VmConlet-style.scss | 74 +- package-lock.json | 1359 +++++++++++++++++ package.json | 1 + 10 files changed, 1483 insertions(+), 87 deletions(-) create mode 100644 .eslintrc.json create mode 100644 org.jdrupes.vmoperator.vmconlet/.eslintrc.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..102ee0d --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,16 @@ +{ + "root": true, + "rules": { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_", + "caughtErrorsIgnorePattern": "^_" + } + ] + }, + "ignorePatterns": ["src/**/*.test.ts", "build/**"] +} + diff --git a/org.jdrupes.vmoperator.vmconlet/.eslintrc.json b/org.jdrupes.vmoperator.vmconlet/.eslintrc.json new file mode 100644 index 0000000..e4f80f1 --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/.eslintrc.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { "project": ["./tsconfig.json"] }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "constructor-super": "off" + } +} + diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/ConditionalInputController.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/ConditionalInputController.ts index e06d245..4f3d8a0 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/ConditionalInputController.ts +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/ConditionalInputController.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { ref, Ref, nextTick } from "vue"; +import { ref, nextTick } from "vue"; /** * A controller for conditionally shown inputs. "Conditionally shown" @@ -26,16 +26,18 @@ import { ref, Ref, nextTick } from "vue"; */ export default class ConditionlInputController { - private submitCallback: (selected: string, value: any) => string | null; + private submitCallback: (selected: string, value: number | null) + => string | null; private readonly inputKey = ref(""); - private startValue: any; + private startValue: string | null = null; private inputElement: HTMLInputElement | null = null; private errorMessage = ref(""); /** * Creates a new controller. */ - constructor(submitCallback: (selected: string, value: string) => string | null) { + constructor(submitCallback: (selected: string, value: number | null) + => string | null) { // this.inputRef = inputRef; this.submitCallback = submitCallback; } @@ -52,7 +54,7 @@ export default class ConditionlInputController { this.inputElement = element; } - startEdit (key: string, value: any) { + startEdit (key: string, value: string) { if (this.inputKey.value != "") { return; } @@ -65,17 +67,17 @@ export default class ConditionlInputController { }); } - endEdit (converter?: (value: string) => any | null) : boolean { + endEdit (converter?: (value: string) => number | null) : boolean { if (typeof converter === 'undefined') { this.inputKey.value = ""; return false; } - let newValue = converter(this.inputElement!.value); + const newValue = converter(this.inputElement!.value); if (newValue === this.startValue) { this.inputKey.value = ""; return false; } - let submitResult = this.submitCallback (this.inputKey.value, newValue); + const submitResult = this.submitCallback (this.inputKey.value, newValue); if (submitResult !== null) { this.errorMessage.value = submitResult; // Neither doing it directly nor doing it with nextTick works. diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/CpuRamChart.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/CpuRamChart.ts index 50525f0..d2bf26b 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/CpuRamChart.ts +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/CpuRamChart.ts @@ -22,6 +22,7 @@ import { formatMemory } from "./MemorySize"; import JGConsole from "jgconsole"; import l10nBundles from "l10nBundles"; import { JGWC } from "jgwc"; +/* eslint-disable @typescript-eslint/no-explicit-any */ export default class CpuRamChart extends Chart { @@ -83,7 +84,7 @@ export default class CpuRamChart extends Chart { } }); - let css = getComputedStyle(canvas); + const css = getComputedStyle(canvas); this.setPropValue("options.plugins.legend.labels.font.family", css.fontFamily); this.setPropValue("options.plugins.legend.labels.color", css.color); this.setPropValue("options.scales.x.ticks.font.family", css.fontFamily); @@ -102,11 +103,12 @@ export default class CpuRamChart extends Chart { } setPropValue(path: string, value: any) { + // eslint-disable-next-line @typescript-eslint/no-this-alias let ptr: any = this; - let segs = path.split("."); - let lastSeg = segs.pop()!; - for (let seg of segs) { - let cur = ptr[seg]; + const segs = path.split("."); + const lastSeg = segs.pop()!; + for (const seg of segs) { + const cur = ptr[seg]; if (!cur) { ptr[seg] = {}; } diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/MemorySize.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/MemorySize.ts index d3795bf..162da1d 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/MemorySize.ts +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/MemorySize.ts @@ -16,15 +16,15 @@ * along with this program. If not, see . */ -let unitMap = new Map(); -let unitMappings = new Array<{ key: string; value: number }>(); -let memorySize = /^(\d+(\.\d+)?)\s*(B|kB|MB|GB|TB|PB|EB|KiB|MiB|GiB|TiB|PiB|EiB)?$/; +const unitMap = new Map(); +const unitMappings = new Array<{ key: string; value: number }>(); +const memorySize = /^(\d+(\.\d+)?)\s*(B|kB|MB|GB|TB|PB|EB|KiB|MiB|GiB|TiB|PiB|EiB)?$/; // SI units and common abbreviations let factor = 1; unitMap.set("", factor); let scale = 1000; -for (let unit of ["B", "kB", "MB", "GB", "TB", "PB", "EB"]) { +for (const unit of ["B", "kB", "MB", "GB", "TB", "PB", "EB"]) { unitMap.set(unit, factor); factor = factor * scale; } @@ -32,7 +32,7 @@ for (let unit of ["B", "kB", "MB", "GB", "TB", "PB", "EB"]) { // Binary units factor = 1024; scale = 1024; -for (let unit of ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]) { +for (const unit of ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB"]) { unitMap.set(unit, factor); factor = factor * scale; } @@ -42,7 +42,7 @@ unitMap.forEach((value: number, key: string) => { unitMappings.sort((a, b) => a.value < b.value ? 1 : a.value > b.value ? -1 : 0); export function formatMemory(size: number): string { - for (let mapping of unitMappings) { + for (const mapping of unitMappings) { if (size >= mapping.value && (size % mapping.value) === 0) { return (size / mapping.value + " " + mapping.key).trim(); @@ -52,7 +52,7 @@ export function formatMemory(size: number): string { } export function parseMemory(value: string): number | null { - let match = value.match(memorySize); + const match = value.match(memorySize); if (!match) { return null; } diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/TimeSeries.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/TimeSeries.ts index 284663a..53a1aa7 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/TimeSeries.ts +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/TimeSeries.ts @@ -36,7 +36,7 @@ export default class TimeSeries { clear() { this.timestamps.length = 0; - for (let values of this.series) { + for (const values of this.series) { values.length = 0; } if (this.onChange) { @@ -66,12 +66,12 @@ export default class TimeSeries { } // Purge - let limit = time.getTime() - this.period; + const limit = time.getTime() - this.period; while (this.timestamps.length > 2 && this.timestamps[0].getTime() < limit && this.timestamps[1].getTime() < limit) { this.timestamps.shift(); - for (let values of this.series) { + for (const values of this.series) { values.shift(); } } diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts index a2cbeec..cf4740a 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts @@ -17,7 +17,7 @@ */ import { - reactive, ref, Ref, createApp, computed, onMounted, watch, nextTick + reactive, ref, Ref, createApp, computed, onMounted, watch } from "vue"; import JGConsole from "jgconsole"; import JgwcPlugin, { JGWC } from "jgwc"; @@ -32,14 +32,17 @@ import "./VmConlet-style.scss"; // For global access declare global { interface Window { - orgJDrupesVmOperatorVmConlet: any; + orgJDrupesVmOperatorVmConlet: { + initPreview?: (previewDom: HTMLElement, isUpdate: boolean) => void, + initView?: (viewDom: HTMLElement, isUpdate: boolean) => void + } } } window.orgJDrupesVmOperatorVmConlet = {}; -let vmInfos = reactive(new Map()); -let vmSummary = reactive({ +const vmInfos = reactive(new Map()); +const vmSummary = reactive({ totalVms: 0, runningVms: 0, usedCpus: 0, @@ -58,19 +61,16 @@ const shortDateTime = (time: Date) => { }; // Cannot be reactive, leads to infinite recursion. -let chartData = new TimeSeries(2); -let chartDateUpdate = ref(null); +const chartData = new TimeSeries(2); +const chartDateUpdate = ref(null); window.orgJDrupesVmOperatorVmConlet.initPreview = (previewDom: HTMLElement, _isUpdate: boolean) => { const app = createApp({ - setup(_props: any) { - const conletId: string - = (previewDom.parentNode!).dataset["conletId"]!; - + setup(_props: object) { let chart: CpuRamChart | null = null; onMounted(() => { - let canvas: HTMLCanvasElement + const canvas: HTMLCanvasElement = previewDom.querySelector(":scope .vmsChart")!; chart = new CpuRamChart(canvas, chartData); }) @@ -86,7 +86,7 @@ window.orgJDrupesVmOperatorVmConlet.initPreview = (previewDom: HTMLElement, const period: Ref = ref("day"); watch(period, (_) => { - let hours = (period.value === "day") ? 24 : 1; + const hours = (period.value === "day") ? 24 : 1; chart?.setPeriod(hours * 3600 * 1000); }); @@ -101,7 +101,7 @@ window.orgJDrupesVmOperatorVmConlet.initPreview = (previewDom: HTMLElement, window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, _isUpdate: boolean) => { const app = createApp({ - setup(_props: any) { + setup(_props: object) { const conletId: string = (viewDom.parentNode!).dataset["conletId"]!; @@ -117,8 +117,8 @@ window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, sortOrder: "up" })); - let filteredData = computed(() => { - let infos = Array.from(vmInfos.values()); + const filteredData = computed(() => { + const infos = Array.from(vmInfos.values()); return controller.filter(infos); }); @@ -129,14 +129,14 @@ window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, const idScope = JGWC.createIdScope(); const detailsByName = reactive(new Set()); - const submitCallback = (selected: string, value: any) => { + const submitCallback = (selected: string, value: number | null) => { if (value === null) { return localize("Illegal format"); } - let vmName = selected.substring(0, selected.lastIndexOf(":")); - let property = selected.substring(selected.lastIndexOf(":") + 1); - var vmDef = vmInfos.get(vmName); - let maxValue = vmDef.spec.vm["maximum" + const vmName = selected.substring(0, selected.lastIndexOf(":")); + const property = selected.substring(selected.lastIndexOf(":") + 1); + const vmDef = vmInfos.get(vmName); + const maxValue = vmDef.spec.vm["maximum" + property.substring(0, 1).toUpperCase() + property.substring(1)]; if (value > maxValue) { return localize("Value is above maximum"); @@ -160,12 +160,13 @@ window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, }; JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", - "updateVm", function(_conletId: String, vmDefinition: any) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + "updateVm", function(_conletId: string, vmDefinition: any) { // Add some short-cuts for table controller vmDefinition.name = vmDefinition.metadata.name; vmDefinition.currentCpus = vmDefinition.status.cpus; vmDefinition.currentRam = Number(vmDefinition.status.ram); - for (let condition of vmDefinition.status.conditions) { + for (const condition of vmDefinition.status.conditions) { if (condition.type === "Running") { vmDefinition.running = condition.status === "True"; vmDefinition.runningConditionSince @@ -177,14 +178,15 @@ JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", }); JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", - "removeVm", function(_conletId: String, vmName: String) { + "removeVm", function(_conletId: string, vmName: string) { vmInfos.delete(vmName); }); JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", - "summarySeries", function(_conletId: String, series: any[]) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + "summarySeries", function(_conletId: string, series: any[]) { chartData.clear(); - for (let entry of series) { + for (const entry of series) { chartData.push(new Date(entry.time.epochSecond * 1000 + entry.time.nano / 1000000), entry.values[0], entry.values[1]); @@ -193,7 +195,8 @@ JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", }); JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmconlet.VmConlet", - "updateSummary", function(_conletId: String, summary: any) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + "updateSummary", function(_conletId: string, summary: any) { chartData.push(new Date(), summary.usedCpus, Number(summary.usedRam)); chartDateUpdate.value = new Date(); Object.assign(vmSummary, summary); diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss index 6ca0333..fcb54e1 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss @@ -38,57 +38,47 @@ } .jdrupes-vmoperator-vmconlet-view-search { - display: flex; + display: flex; + justify-content: flex-end -} - -.jdrupes-vmoperator-vmconlet-view-search form { - white-space: nowrap; -} - -.jdrupes-vmoperator-vmconlet-view-action-list { - white-space: nowrap; -} - -.jdrupes-vmoperator-vmconlet-view-action-list [role=button]:not(:last-child) { - margin-right: 0.5em; -} - -.jdrupes-vmoperator-vmconlet-view td { - vertical-align: top; - &[tabindex] { - outline: 1px solid var(--primary); - cursor: text; + form { + white-space: nowrap; } } -.jdrupes-vmoperator-vmconlet-view td:not([colspan]):first-child { - white-space: nowrap; -} - -.jdrupes-vmoperator-vmconlet-view table td.details { - padding-left: 1em; -} - .jdrupes-vmoperator-vmconlet-view-table { - td.column-running { - text-align: center; + td { + vertical-align: top; - span { - &.fa-check { - color: var(--success); - } + &[tabindex] { + outline: 1px solid var(--primary); + cursor: text; + } + + &:not([colspan]):first-child { + white-space: nowrap; + } - &.fa-close { - color: var(--danger); + &.column-running { + text-align: center; + + span { + &.fa-check { + color: var(--success); + } + + &.fa-close { + color: var(--danger); + } } } } - + td.details { + padding-left: 1em; + table { - td:nth-child(2) { min-width: 7em; @@ -104,3 +94,11 @@ } } } + +.jdrupes-vmoperator-vmconlet-view-action-list { + white-space: nowrap; + + [role=button]:not(:last-child) { + margin-right: 0.5em; + } +} diff --git a/package-lock.json b/package-lock.json index 1a31645..85f62bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-terser": "^0.4.0", + "@typescript-eslint/eslint-plugin": "^6.9.1", "documentation": "^14.0.1", "install": "^0.13.0", "jsdoc": "^4.0.2", @@ -26,6 +27,16 @@ "typescript": "^5.2.2" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -451,12 +462,196 @@ "node": ">=6.9.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", + "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true, + "peer": true + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -527,6 +722,41 @@ "node": ">=v12.0.0" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@npmcli/fs": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", @@ -881,6 +1111,12 @@ "@types/unist": "^2" } }, + "node_modules/@types/json-schema": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", + "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "dev": true + }, "node_modules/@types/linkify-it": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.3.tgz", @@ -942,6 +1178,12 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, + "node_modules/@types/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "dev": true + }, "node_modules/@types/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", @@ -954,6 +1196,302 @@ "integrity": "sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==", "dev": true }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz", + "integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/type-utils": "6.9.1", + "@typescript-eslint/utils": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", + "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz", + "integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz", + "integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/utils": "6.9.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz", + "integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz", + "integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz", + "integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/typescript-estree": "6.9.1", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz", + "integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.9.1", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "peer": true + }, "node_modules/@vue/compiler-core": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", @@ -1041,6 +1579,16 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -1078,6 +1626,23 @@ "node": ">=8" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1146,6 +1711,15 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -1310,6 +1884,16 @@ "node": ">=12" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -1820,6 +2404,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "peer": true + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -1853,6 +2444,31 @@ "node": ">=0.3.1" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/doctrine-temporary-fork": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine-temporary-fork/-/doctrine-temporary-fork-2.1.0.tgz", @@ -2054,6 +2670,352 @@ "node": ">=8" } }, + "node_modules/eslint": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", + "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.52.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "peer": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", @@ -2081,6 +3043,65 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "peer": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "peer": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "peer": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2126,6 +3147,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flat-cache": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", + "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", + "dev": true, + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true, + "peer": true + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -2310,6 +3353,26 @@ "integrity": "sha512-qpPnUKkWnz8NESjrCvnlGklsgiQzlq+rcCxoG5uNQ+dNA7cFMCmn231slLAwS2N/PlkzZ3COL8CcS10jXmLHqg==", "dev": true }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globule": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", @@ -2372,6 +3435,12 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -2679,6 +3748,15 @@ "postcss": "^8.1.0" } }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/immutable": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", @@ -2697,6 +3775,33 @@ "node": ">=8" } }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/import-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", @@ -2905,6 +4010,16 @@ "node": ">=0.12.0" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -3054,12 +4169,33 @@ "node": ">=4" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "peer": true + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "peer": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "peer": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -3090,6 +4226,16 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -3127,6 +4273,20 @@ "@babel/traverse": "^7.10.5" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -3193,6 +4353,13 @@ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "peer": true + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -3830,6 +4997,15 @@ "semver": "bin/semver" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/micromark": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/micromark/-/micromark-3.2.0.tgz", @@ -4393,6 +5569,19 @@ } ] }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -4581,6 +5770,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -7653,6 +8848,24 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "peer": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -7744,6 +8957,19 @@ "node": ">=6" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-filepath": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", @@ -7854,6 +9080,15 @@ "node": ">=0.10.0" } }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -8516,6 +9751,16 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -8566,6 +9811,36 @@ "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==", "dev": true }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", @@ -8844,6 +10119,16 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -9106,6 +10391,29 @@ "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", "dev": true }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -9310,6 +10618,15 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -9684,6 +11001,13 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "peer": true + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -9740,12 +11064,37 @@ "integrity": "sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==", "dev": true }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", @@ -10022,6 +11371,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 1e01398..5d4c1f3 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-terser": "^0.4.0", + "@typescript-eslint/eslint-plugin": "^6.9.1", "documentation": "^14.0.1", "install": "^0.13.0", "jsdoc": "^4.0.2", From 350446b5227d4209ceca2460fc6aab259eeac08d Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Wed, 1 Nov 2023 17:12:29 +0100 Subject: [PATCH 085/462] Bugfix/ts style2 (#18) --- .eslintrc.json | 2 +- org.jdrupes.vmoperator.vmconlet/.eslintignore | 1 + org.jdrupes.vmoperator.vmconlet/rollup.config.mjs | 2 +- .../jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss | 3 +-- 4 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 org.jdrupes.vmoperator.vmconlet/.eslintignore diff --git a/.eslintrc.json b/.eslintrc.json index 102ee0d..943329e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,6 +11,6 @@ } ] }, - "ignorePatterns": ["src/**/*.test.ts", "build/**"] + "ignorePatterns": ["src/**/*.test.ts", "build/**/*"] } diff --git a/org.jdrupes.vmoperator.vmconlet/.eslintignore b/org.jdrupes.vmoperator.vmconlet/.eslintignore new file mode 100644 index 0000000..139d3ee --- /dev/null +++ b/org.jdrupes.vmoperator.vmconlet/.eslintignore @@ -0,0 +1 @@ +rollup.config.mjs diff --git a/org.jdrupes.vmoperator.vmconlet/rollup.config.mjs b/org.jdrupes.vmoperator.vmconlet/rollup.config.mjs index f1273ad..83964f7 100644 --- a/org.jdrupes.vmoperator.vmconlet/rollup.config.mjs +++ b/org.jdrupes.vmoperator.vmconlet/rollup.config.mjs @@ -16,7 +16,7 @@ let pathsMap = { } export default { - external: ['vue', 'aash-plugin', 'jgconsole', 'jgwc', 'l10nBundles'], + external: ['aash-plugin', 'jgconsole', 'jgwc', 'l10nBundles', 'vue', 'chartjs'], input: "src/" + packagePath + "/browser/" + baseName + "-functions.ts", output: [ { diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss index fcb54e1..8f85eb6 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss @@ -39,8 +39,7 @@ .jdrupes-vmoperator-vmconlet-view-search { display: flex; - - justify-content: flex-end + justify-content: flex-end; form { white-space: nowrap; From fdacb1e3d24303125456d41524bc47fcf726bfb0 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Wed, 1 Nov 2023 20:23:03 +0100 Subject: [PATCH 086/462] Upgrade sysinfo conlet. --- org.jdrupes.vmoperator.manager/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index faa87d1..d1096ab 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -24,8 +24,8 @@ dependencies { implementation 'org.jgrapes:org.jgrapes.webconlet.locallogin:[1.0.0,2)' runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.locallogin:[0.1.0,2)' - runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.sysinfo:[1.2.0,2)' - runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.logviewer:[0.1.0-SNAPSHOT,2)' + runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.sysinfo:[1.4.0,2)' + runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.logviewer:[0.2.0,2)' runtimeOnly 'com.electronwill.night-config:yaml:[3.6.7,3.7)' runtimeOnly 'org.eclipse.angus:angus-activation:[1.0.0,2.0.0)' From 91a9c8605c7d478b0f863fc4ba706172ef794b46 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 3 Nov 2023 22:13:02 +0100 Subject: [PATCH 087/462] Upgrade webconsole base. --- org.jdrupes.vmoperator.manager/build.gradle | 2 +- org.jdrupes.vmoperator.vmconlet/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index d1096ab..9385a6c 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -18,7 +18,7 @@ dependencies { implementation 'org.jgrapes:org.jgrapes.http:[3.1.0,4)' implementation 'org.jgrapes:org.jgrapes.util:[1.31.0,2)' - implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.2.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.3.0,2)' implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.5.0,2)' implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.0.0,2)' implementation 'org.jgrapes:org.jgrapes.webconlet.locallogin:[1.0.0,2)' diff --git a/org.jdrupes.vmoperator.vmconlet/build.gradle b/org.jdrupes.vmoperator.vmconlet/build.gradle index 0adb40e..ab667f5 100644 --- a/org.jdrupes.vmoperator.vmconlet/build.gradle +++ b/org.jdrupes.vmoperator.vmconlet/build.gradle @@ -5,7 +5,7 @@ plugins { dependencies { implementation project(':org.jdrupes.vmoperator.manager.events') - implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.2.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.3.0,2)' implementation 'org.jgrapes:org.jgrapes.webconsole.provider.vue:[1,2)' implementation 'org.jgrapes:org.jgrapes.webconsole.provider.jgwcvuecomponents:[1.2,2)' implementation 'org.jgrapes:org.jgrapes.webconsole.provider.chartjs:[1.2,2)' From 4122fa25d21a0ef94618066eb58e89f10c94b019 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 3 Nov 2023 22:13:48 +0100 Subject: [PATCH 088/462] Handle maximum CPUs properly. --- .../vmoperator/vmconlet/VmConlet-view.ftl.html | 2 +- .../vmconlet/browser/VmConlet-functions.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html index 0e4bb26..913f45d 100644 --- a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html @@ -70,7 +70,7 @@ - + diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts index cf4740a..44d6471 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-functions.ts @@ -147,9 +147,22 @@ window.orgJDrupesVmOperatorVmConlet.initView = (viewDom: HTMLElement, const cic = new ConditionlInputController(submitCallback); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const maximumCpus = (vmDef: any) => { + if (vmDef.spec.vm["maximumCpus"]) { + return vmDef.spec.vm.maximumCpus; + } + const topo = vmDef.spec.vm.cpuTopology; + return Math.max(1, topo.coresPerDie) + * Math.max(1, topo.diesPerSocket) + * Math.max(1, topo.sockets) + * Math.max(1, topo.threadsPerCore); + } + return { controller, vmInfos, filteredData, detailsByName, localize, shortDateTime, formatMemory, vmAction, cic, parseMemory, + maximumCpus, scopedId: (id: string) => { return idScope.scopedId(id); } }; } From bf1e05d5979f3bdf6f40b8198401f565c3a26913 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 4 Nov 2023 12:39:18 +0100 Subject: [PATCH 089/462] Rename method. --- .../src/org/jdrupes/vmoperator/vmconlet/VmConlet.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java index 4f9c0a9..f096485 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java @@ -195,7 +195,7 @@ public class VmConlet extends FreeMarkerConlet { } } } else { - var vmDef = prepareForSending(event); + var vmDef = convertQuantities(event); var def = JsonBeanDecoder.create(vmDef.getRaw().toString()) .readObject(); for (var entry : conletIdsByConsoleConnection().entrySet()) { @@ -217,7 +217,7 @@ public class VmConlet extends FreeMarkerConlet { } @SuppressWarnings("PMD.AvoidDuplicateLiterals") - private DynamicKubernetesObject prepareForSending(VmDefChanged event) { + private DynamicKubernetesObject convertQuantities(VmDefChanged event) { // Clone and remove managed fields var vmDef = new DynamicKubernetesObject( event.vmDefinition().getRaw().deepCopy()); From 01e392fc9dba3b1f3d72e1e2e13c6f7897f9ca31 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 4 Nov 2023 12:40:00 +0100 Subject: [PATCH 090/462] Ensure that json does not change after association with channel. --- .../org/jdrupes/vmoperator/manager/VmWatcher.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java index a09b4b2..7f8b0c4 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java @@ -277,11 +277,14 @@ public class VmWatcher extends Component { DynamicKubernetesApi vmCrApi = new DynamicKubernetesApi(VM_OP_GROUP, apiVersion, vmsCrd.getName(), channel.client()); var curVmDef = K8s.get(vmCrApi, metadata); - curVmDef.ifPresent(def -> channel.setVmDefinition(def)); + curVmDef.ifPresent(def -> { + // Augment with "dynamic" data and associate with channel + addDynamicData(channel.client(), def); + channel.setVmDefinition(def); + }); - // Get eventual definition and augment with "dynamic" data. + // Get eventual definition to use var vmDef = curVmDef.orElse(channel.vmDefinition()); - addDynamicData(channel, vmDef); // Create and fire event channel.pipeline().fire(new VmDefChanged(VmDefChanged.Type @@ -291,7 +294,7 @@ public class VmWatcher extends Component { vmsCrd, vmDef), channel); } - private void addDynamicData(VmChannel channel, + private void addDynamicData(ApiClient client, DynamicKubernetesObject vmDef) { var rootNode = GsonPtr.to(vmDef.getRaw()).get(JsonObject.class); rootNode.addProperty("nodeName", ""); @@ -313,8 +316,7 @@ public class VmWatcher extends Component { podSearch.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ",app.kubernetes.io/component=" + APP_NAME + ",app.kubernetes.io/instance=" + vmDef.getMetadata().getName()); - var podList = K8s.podApi(channel.client()).list(namespaceToWatch, - podSearch); + var podList = K8s.podApi(client).list(namespaceToWatch, podSearch); podList.getObject().getItems().stream().forEach(pod -> { rootNode.addProperty("nodeName", pod.getSpec().getNodeName()); }); From 6e2d23d979a152f4fe08f48ace94ceb10066d8e8 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 4 Nov 2023 14:37:26 +0100 Subject: [PATCH 091/462] Use default log settings by default. --- deploy/vmop-config-map.yaml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/deploy/vmop-config-map.yaml b/deploy/vmop-config-map.yaml index 2b94f19..69df680 100644 --- a/deploy/vmop-config-map.yaml +++ b/deploy/vmop-config-map.yaml @@ -8,17 +8,3 @@ metadata: data: config.yaml: | "/Manager": {} - - logging.properties: | - handlers=java.util.logging.ConsoleHandler, \ - org.jgrapes.webconlet.logviewer.LogViewerHandler - - org.jgrapes.level=FINE - org.jdrupes.vmoperator.manager.level=FINE - - java.util.logging.ConsoleHandler.level=ALL - java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter - java.util.logging.SimpleFormatter.format=%1$tb %1$td %1$tT %4$s %5$s%6$s%n - - org.jgrapes.webconlet.logviewer.LogViewerHandler.level=FINE - \ No newline at end of file From 886c5b436e9bbf6fb8be8e53856ebbdf88882a43 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 4 Nov 2023 14:41:12 +0100 Subject: [PATCH 092/462] Restrict viewer log level to config. --- deploy/vmop-config-map.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/deploy/vmop-config-map.yaml b/deploy/vmop-config-map.yaml index 69df680..12d9ccf 100644 --- a/deploy/vmop-config-map.yaml +++ b/deploy/vmop-config-map.yaml @@ -8,3 +8,17 @@ metadata: data: config.yaml: | "/Manager": {} + + logging.properties: | + handlers=java.util.logging.ConsoleHandler, \ + org.jgrapes.webconlet.logviewer.LogViewerHandler + + org.jgrapes.level=FINE + org.jdrupes.vmoperator.manager.level=FINE + + java.util.logging.ConsoleHandler.level=ALL + java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter + java.util.logging.SimpleFormatter.format=%1$tb %1$td %1$tT %4$s %5$s%6$s%n + + org.jgrapes.webconlet.logviewer.LogViewerHandler.level=CONFIG + \ No newline at end of file From 7dd9921dbf51671eb0ebc760a9b5772c055b8c33 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 4 Nov 2023 14:53:48 +0100 Subject: [PATCH 093/462] Provide default logging configuration as resource. --- .../org/jdrupes/vmoperator/manager}/logging.properties | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename org.jdrupes.vmoperator.manager/{ => resources/org/jdrupes/vmoperator/manager}/logging.properties (100%) diff --git a/org.jdrupes.vmoperator.manager/logging.properties b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/logging.properties similarity index 100% rename from org.jdrupes.vmoperator.manager/logging.properties rename to org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/logging.properties From 203ea00c7b40e6258a5f49b10c8a7ef55a6dd69e Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 5 Nov 2023 12:37:41 +0100 Subject: [PATCH 094/462] Fix checkstyle configuration. --- checkstyle.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/checkstyle.xml b/checkstyle.xml index b5a60d3..015ef09 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -50,10 +50,9 @@ - - + From 03dfa7a4d8bc5cb51154ccfbe0c6577d5e8b91a9 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 5 Nov 2023 15:08:51 +0100 Subject: [PATCH 095/462] Provide more context information. --- dev-example/config.yaml | 2 + dev-example/kustomization.yaml | 1 + .../vmoperator/manager/console-brand.ftl.html | 5 +- .../vmoperator/manager/Controller.java | 2 + .../jdrupes/vmoperator/manager/Manager.java | 80 ++++++++++++++++--- .../vmoperator/manager/package-info.java | 1 + 6 files changed, 79 insertions(+), 12 deletions(-) diff --git a/dev-example/config.yaml b/dev-example/config.yaml index 4a471a8..cf43692 100644 --- a/dev-example/config.yaml +++ b/dev-example/config.yaml @@ -1,6 +1,8 @@ # Used for running manager outside Kubernetes. # Keep in sync with kustomize.yaml "/Manager": + # If provided, is shown at top left before namespace + # clusterName: "test" # The controller manages the VM "/Controller": namespace: vmop-dev diff --git a/dev-example/kustomization.yaml b/dev-example/kustomization.yaml index ae36fe1..70c6ae6 100644 --- a/dev-example/kustomization.yaml +++ b/dev-example/kustomization.yaml @@ -29,6 +29,7 @@ patches: # Keep in sync with config.yaml config.yaml: | "/Manager": + # clusterName: "test" "/Controller": namespace: vmop-dev "/Reconciler": diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-brand.ftl.html b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-brand.ftl.html index a81ee0a..9c9de88 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-brand.ftl.html +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/console-brand.ftl.html @@ -1,2 +1,5 @@ ${_("consoleTitle")} \ No newline at end of file + src="${renderSupport.consoleResource('VM-Operator.svg')}" + >${_("consoleTitle")}  + (<#if clusterName()??>${clusterName() + "/"}${ namespace() }) \ No newline at end of file diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java index 3f7badb..d865ba4 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java @@ -138,6 +138,8 @@ public class Controller extends Component { .of("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); if (Files.isReadable(path)) { namespace = Files.lines(path).findFirst().orElse(null); + fire(new ConfigurationUpdate().add(componentPath(), "namespace", + namespace)); } } if (namespace == null) { diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java index d39718f..1175d8b 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java @@ -18,6 +18,8 @@ package org.jdrupes.vmoperator.manager; +import freemarker.template.TemplateMethodModelEx; +import freemarker.template.TemplateModelException; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -27,6 +29,9 @@ import java.net.URISyntaxException; import java.nio.file.Files; import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; @@ -54,6 +59,7 @@ import org.jgrapes.net.SocketServer; import org.jgrapes.util.ComponentCollector; import org.jgrapes.util.FileSystemWatcher; import org.jgrapes.util.YamlConfigurationStore; +import org.jgrapes.util.events.ConfigurationUpdate; import org.jgrapes.util.events.WatchFile; import org.jgrapes.webconlet.locallogin.LoginConlet; import org.jgrapes.webconsole.base.BrowserLocalBackedKVStore; @@ -69,9 +75,13 @@ import org.jgrapes.webconsole.vuejs.VueJsConsoleWeblet; /** * The application class. */ +@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports" }) public class Manager extends Component { + private static String version; private static Manager app; + private String clusterName; + private String namespace = "unknown"; /** * Instantiates a new manager. @@ -79,26 +89,26 @@ public class Manager extends Component { * * @throws IOException Signals that an I/O exception has occurred. */ - @SuppressWarnings("PMD.TooFewBranchesForASwitchStatement") + @SuppressWarnings({ "PMD.TooFewBranchesForASwitchStatement", + "PMD.NcssCount" }) public Manager(CommandLine cmdLine) throws IOException { + super(new NamedChannel("manager")); // Prepare component tree attach(new NioDispatcher()); - Channel mgrChannel = new NamedChannel("manager"); - attach(new FileSystemWatcher(mgrChannel)); - attach(new Controller(mgrChannel)); + attach(new FileSystemWatcher(channel())); + attach(new Controller(channel())); // Configuration store with file in /etc/opt (default) File cfgFile = new File(cmdLine.getOptionValue('c', - "/etc/opt/" + VM_OP_NAME.replace("-", "") + "/config.yaml")) - .getCanonicalFile(); + "/etc/opt/" + VM_OP_NAME.replace("-", "") + "/config.yaml")); logger.config(() -> "Using configuration from: " + cfgFile.getPath()); // Don't rely on night config to produce a good exception // for this simple case if (!Files.isReadable(cfgFile.toPath())) { throw new IOException("Cannot read configuration file " + cfgFile); } - attach(new YamlConfigurationStore(mgrChannel, cfgFile, false)); - fire(new WatchFile(cfgFile.toPath())); + attach(new YamlConfigurationStore(channel(), cfgFile, false)); + fire(new WatchFile(cfgFile.toPath()), channel()); // Prepare GUI Channel httpTransport = new NamedChannel("guiTransport"); @@ -126,7 +136,12 @@ public class Manager extends Component { return; } ConsoleWeblet consoleWeblet = guiHttpServer - .attach(new VueJsConsoleWeblet(httpChannel, Channel.SELF, rootUri)) + .attach(new VueJsConsoleWeblet(httpChannel, Channel.SELF, rootUri) { + @Override + protected Map createConsoleBaseModel() { + return augmentBaseModel(super.createConsoleBaseModel()); + } + }) .prependClassTemplateLoader(getClass()) .prependResourceBundleProvider(getClass()) .prependConsoleResourceProvider(getClass()); @@ -154,6 +169,47 @@ public class Manager extends Component { })); } + private Map augmentBaseModel(Map base) { + base.put("version", version); + base.put("clusterName", new TemplateMethodModelEx() { + @Override + public Object exec(@SuppressWarnings("rawtypes") List arguments) + throws TemplateModelException { + return clusterName; + } + }); + base.put("namespace", new TemplateMethodModelEx() { + @Override + public Object exec(@SuppressWarnings("rawtypes") List arguments) + throws TemplateModelException { + return namespace; + } + }); + return base; + } + + /** + * Configure the component. + * + * @param event the event + */ + @Handler + @SuppressWarnings("PMD.DataflowAnomalyAnalysis") + public void onConfigurationUpdate(ConfigurationUpdate event) { + event.structured(componentPath()).ifPresent(c -> { + if (c.containsKey("clusterName")) { + clusterName = (String) c.get("clusterName"); + } else { + clusterName = null; + } + }); + event.structured(componentPath() + "/Controller").ifPresent(c -> { + if (c.containsKey("namespace")) { + namespace = (String) c.get("namespace"); + } + }); + } + /** * Log the exception when a handling error is reported. * @@ -207,8 +263,10 @@ public class Manager extends Component { try { // Instance logger is not available yet. var logger = Logger.getLogger(Manager.class.getName()); - logger.config(() -> "Version: " - + Manager.class.getPackage().getImplementationVersion()); + version = Optional.ofNullable( + Manager.class.getPackage().getImplementationVersion()) + .orElse("unknown"); + logger.config(() -> "Version: " + version); logger.config(() -> "running on " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.version") + ")" diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java index 48bc158..54d4efe 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java @@ -140,6 +140,7 @@ * mgr .left. [FileSystemWatcher] * mgr .right. [YamlConfigurationStore] * mgr .. [Controller] + * mgr .up. [Manager] * mgr .up. [VmWatcher] * mgr .. [Reconciler] * From 9b89bf8a968a118fd60d7b35c2c7f0e097702e55 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 8 Jan 2024 15:33:43 +0100 Subject: [PATCH 096/462] Remove mavenLocal (should never have been committed). --- .../src/org.jdrupes.vmoperator.java-common-conventions.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle index 50aebae..e09814c 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle @@ -21,7 +21,6 @@ plugins { repositories { // Use Maven Central for resolving dependencies. mavenCentral() - mavenLocal() } dependencies { From 545106510ef8736cf900bfac31bf92114614fe81 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 8 Jan 2024 22:58:43 +0100 Subject: [PATCH 097/462] Update. --- .settings/org.eclipse.buildship.core.prefs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs index d0fed22..44c5061 100644 --- a/.settings/org.eclipse.buildship.core.prefs +++ b/.settings/org.eclipse.buildship.core.prefs @@ -1,11 +1,11 @@ -arguments=--init-script /home/mnl/.config/Code/User/globalStorage/redhat.java/1.18.0/config_linux/org.eclipse.osgi/51/0/.cp/gradle/init/init.gradle --init-script /home/mnl/.config/Code/User/globalStorage/redhat.java/1.18.0/config_linux/org.eclipse.osgi/51/0/.cp/gradle/protobuf/init.gradle +arguments=--init-script /home/mnl/.config/Code/User/globalStorage/redhat.java/1.24.0/config_linux/org.eclipse.osgi/55/0/.cp/gradle/init/init.gradle --init-script /home/mnl/.config/Code/User/globalStorage/redhat.java/1.24.0/config_linux/org.eclipse.osgi/55/0/.cp/gradle/protobuf/init.gradle auto.sync=false build.scans.enabled=false connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.project.dir= eclipse.preferences.version=1 gradle.user.home= -java.home= +java.home=/usr/lib/jvm/java-17-openjdk-17.0.8.0.7-1.fc37.x86_64 jvm.arguments= offline.mode=false override.workspace.settings=true From 41a0ef1adb8d6e2c657c9075dd64f7f715d055a4 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 9 Jan 2024 10:19:04 +0100 Subject: [PATCH 098/462] Fail if API server cannot be contacted. --- .../vmoperator/runner/qemu/Runner.java | 17 ++++++++ .../vmoperator/runner/qemu/StatusUpdater.java | 23 +++++++--- .../vmoperator/runner/qemu/events/Exit.java | 43 +++++++++++++++++++ 3 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/Exit.java diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index 4e4ba18..b93ffd6 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -52,6 +52,7 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import static org.jdrupes.vmoperator.common.Constants.APP_NAME; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCont; +import org.jdrupes.vmoperator.runner.qemu.events.Exit; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; import org.jdrupes.vmoperator.runner.qemu.events.QmpConfigured; import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; @@ -185,6 +186,7 @@ public class Runner extends Component { = "Standard-VM-latest.ftl.yaml"; private static final String SAVED_TEMPLATE = "VM.ftl.yaml"; private static final String FW_VARS = "fw-vars.fd"; + private static int exitStatus; private EventPipeline rep; private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); @@ -582,6 +584,16 @@ public class Runner extends Component { "The VM has been shut down")); } + /** + * On exit. + * + * @param event the event + */ + @Handler + public void onExit(Exit event) { + exitStatus = event.exitStatus(); + } + private void shutdown() { if (state != State.TERMINATING) { fire(new Stop()); @@ -650,6 +662,11 @@ public class Runner extends Component { // Start the application Components.start(app); + + // Wait for (regular) termination + Components.awaitExhaustion(); + System.exit(exitStatus); + } catch (IOException | InterruptedException | org.apache.commons.cli.ParseException e) { Logger.getLogger(Runner.class.getName()).log(Level.SEVERE, e, diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index 19e252f..4edcb46 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -41,6 +41,7 @@ import java.util.logging.Level; import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM; import org.jdrupes.vmoperator.runner.qemu.events.BalloonChangeEvent; +import org.jdrupes.vmoperator.runner.qemu.events.Exit; import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus; import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; @@ -57,6 +58,7 @@ import org.jgrapes.util.events.InitialConfiguration; /** * Updates the CR status. */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") public class StatusUpdater extends Component { private static final Set RUNNING_STATES @@ -135,12 +137,21 @@ public class StatusUpdater extends Component { * @throws ApiException */ @Handler - @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", - "PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals" }) - public void onStart(Start event) throws IOException, ApiException { + public void onStart(Start event) { if (namespace == null) { return; } + try { + initVmCrApi(event); + } catch (IOException | ApiException e) { + logger.log(Level.SEVERE, e, + () -> "Cannot access VM's CR, terminating."); + event.cancel(true); + fire(new Exit(1)); + } + } + + private void initVmCrApi(Start event) throws IOException, ApiException { var client = Config.defaultClient(); var apis = new ApisApi(client).getAPIVersions(); var crdVersions = apis.getGroups().stream() @@ -155,6 +166,7 @@ public class StatusUpdater extends Component { if (crdApiRes.isEmpty()) { continue; } + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") var crApi = new DynamicKubernetesApi(VM_OP_GROUP, crdVersion, crdApiRes.get().getName(), client); var vmCr = crApi.get(namespace, vmName); @@ -166,8 +178,9 @@ public class StatusUpdater extends Component { } } if (vmCrApi == null) { - logger.warning(() -> "Cannot find VM's CR, status will not" - + " be updated."); + logger.severe(() -> "Cannot find VM's CR, terminating."); + event.cancel(true); + fire(new Exit(1)); } } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/Exit.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/Exit.java new file mode 100644 index 0000000..bb608f6 --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/Exit.java @@ -0,0 +1,43 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.runner.qemu.events; + +import org.jgrapes.core.events.Stop; + +/** + * Like {@link Stop}, but sets an exit status. + */ +@SuppressWarnings("PMD.ShortClassName") +public class Exit extends Stop { + + private final int exitStatus; + + /** + * Instantiates a new exit. + * + * @param exitStatus the exit status + */ + public Exit(int exitStatus) { + this.exitStatus = exitStatus; + } + + public int exitStatus() { + return exitStatus; + } +} From 245fad39a94ea3af0a3b2080bccf63f0e827b5f5 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 9 Jan 2024 13:56:19 +0100 Subject: [PATCH 099/462] Add event generation. --- .../org/jdrupes/vmoperator/common/K8s.java | 17 ++++++++++++ .../vmoperator/runner/qemu/StatusUpdater.java | 27 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java index 5a87ecd..f61b431 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java @@ -30,12 +30,14 @@ import io.kubernetes.client.openapi.models.V1ConfigMap; import io.kubernetes.client.openapi.models.V1ConfigMapList; import io.kubernetes.client.openapi.models.V1GroupVersionForDiscovery; import io.kubernetes.client.openapi.models.V1ObjectMeta; +import io.kubernetes.client.openapi.models.V1ObjectReference; import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim; import io.kubernetes.client.openapi.models.V1PersistentVolumeClaimList; import io.kubernetes.client.openapi.models.V1Pod; import io.kubernetes.client.openapi.models.V1PodList; import io.kubernetes.client.util.generic.GenericKubernetesApi; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; +import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; import io.kubernetes.client.util.generic.options.DeleteOptions; import io.kubernetes.client.util.generic.options.PatchOptions; import java.util.Optional; @@ -204,4 +206,19 @@ public class K8s { return response.getObject(); } + /** + * Create an object reference. + * + * @param object the object + * @return the v 1 object reference + */ + public static V1ObjectReference + objectReference(DynamicKubernetesObject object) { + return new V1ObjectReference().apiVersion(object.getApiVersion()) + .kind(object.getKind()) + .namespace(object.getMetadata().getNamespace()) + .name(object.getMetadata().getName()) + .resourceVersion(object.getMetadata().getResourceVersion()) + .uid(object.getMetadata().getUid()); + } } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index 4edcb46..542fa06 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -24,8 +24,11 @@ import io.kubernetes.client.custom.Quantity.Format; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.ApisApi; import io.kubernetes.client.openapi.apis.CustomObjectsApi; +import io.kubernetes.client.openapi.apis.EventsV1Api; +import io.kubernetes.client.openapi.models.EventsV1Event; import io.kubernetes.client.openapi.models.V1APIGroup; import io.kubernetes.client.openapi.models.V1GroupVersionForDiscovery; +import io.kubernetes.client.openapi.models.V1ObjectMeta; import io.kubernetes.client.util.Config; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; @@ -34,12 +37,15 @@ import java.math.BigDecimal; import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; +import java.time.OffsetDateTime; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.logging.Level; +import static org.jdrupes.vmoperator.common.Constants.APP_NAME; import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM; +import org.jdrupes.vmoperator.common.K8s; import org.jdrupes.vmoperator.runner.qemu.events.BalloonChangeEvent; import org.jdrupes.vmoperator.runner.qemu.events.Exit; import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus; @@ -67,6 +73,7 @@ public class StatusUpdater extends Component { private String namespace; private String vmName; private DynamicKubernetesApi vmCrApi; + private EventsV1Api evtsApi; private long observedGeneration; /** @@ -149,6 +156,14 @@ public class StatusUpdater extends Component { event.cancel(true); fire(new Exit(1)); } + try { + evtsApi = new EventsV1Api(Config.defaultClient()); + } catch (IOException e) { + logger.log(Level.SEVERE, e, + () -> "Cannot access events API, terminating."); + event.cancel(true); + fire(new Exit(1)); + } } private void initVmCrApi(Start event) throws IOException, ApiException { @@ -252,6 +267,18 @@ public class StatusUpdater extends Component { } return status; }).throwsApiException(); + + // Log event + var evt = new EventsV1Event().kind("Event") + .metadata(new V1ObjectMeta().namespace(namespace) + .generateName("vmrunner-")) + .reportingController(VM_OP_GROUP + "/" + APP_NAME) + .reportingInstance(vmCr.getMetadata().getName()) + .eventTime(OffsetDateTime.now()).type("Normal") + .regarding(K8s.objectReference(vmCr)) + .action("StatusUpdate").reason(event.reason()) + .note(event.message()); + evtsApi.createNamespacedEvent(namespace, evt, null, null, null, null); } private void updateRunningCondition(RunnerStateChange event, From f6b70684cae686908c4c3f4f02a45b0cbb85c70e Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 9 Jan 2024 14:04:58 +0100 Subject: [PATCH 100/462] Avoid redundant Stop event. --- .../src/org/jdrupes/vmoperator/runner/qemu/Runner.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index b93ffd6..979ac8e 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -42,6 +42,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; @@ -595,7 +596,7 @@ public class Runner extends Component { } private void shutdown() { - if (state != State.TERMINATING) { + if (!Set.of(State.TERMINATING, State.STOPPED).contains(state)) { fire(new Stop()); } try { From 218825fb8c0c42a94c6372811c792979bd29248c Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 9 Jan 2024 18:11:32 +0100 Subject: [PATCH 101/462] Update build. --- .../org/jdrupes/vmoperator/runner/qemu/Containerfile.arch | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch index 379537b..e3055c0 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch @@ -1,8 +1,9 @@ -FROM archlinux/archlinux +FROM archlinux/archlinux:latest RUN systemd-firstboot -RUN pacman -Suy --noconfirm \ +RUN pacman-key --init \ + && pacman -Sy --noconfirm archlinux-keyring && pacman -Su --noconfirm \ && pacman -S --noconfirm which qemu-full virtiofsd \ edk2-ovmf swtpm iproute2 bridge-utils jre17-openjdk-headless \ && pacman -Scc --noconfirm From 5ec67ba61aff0292cc636a128e16b04962fbc423 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 9 Jan 2024 18:46:08 +0100 Subject: [PATCH 102/462] Allow event generation. --- deploy/vmrunner-role.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/deploy/vmrunner-role.yaml b/deploy/vmrunner-role.yaml index 54e8742..8aea4e2 100644 --- a/deploy/vmrunner-role.yaml +++ b/deploy/vmrunner-role.yaml @@ -18,3 +18,9 @@ rules: - vms/status verbs: - patch +- apiGroups: + - events.k8s.io + resources: + - events + verbs: + - create From d85c957b3345d24a33319ba17076ecbd5050058e Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 12 Jan 2024 20:34:37 +0100 Subject: [PATCH 103/462] Exit with error on connection failure with kubernetes API. --- .../vmoperator/manager/events/Exit.java | 43 +++++++++++++++++++ .../vmoperator/manager/Controller.java | 4 +- .../jdrupes/vmoperator/manager/Manager.java | 16 +++++++ .../jdrupes/vmoperator/manager/VmWatcher.java | 14 +++++- 4 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/Exit.java diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/Exit.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/Exit.java new file mode 100644 index 0000000..1c11a4e --- /dev/null +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/Exit.java @@ -0,0 +1,43 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.manager.events; + +import org.jgrapes.core.events.Stop; + +/** + * Like {@link Stop}, but sets an exit status. + */ +@SuppressWarnings("PMD.ShortClassName") +public class Exit extends Stop { + + private final int exitStatus; + + /** + * Instantiates a new exit. + * + * @param exitStatus the exit status + */ + public Exit(int exitStatus) { + this.exitStatus = exitStatus; + } + + public int exitStatus() { + return exitStatus; + } +} diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java index d865ba4..589affc 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java @@ -30,6 +30,7 @@ import java.util.logging.Level; import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM; import org.jdrupes.vmoperator.common.K8s; +import org.jdrupes.vmoperator.manager.events.Exit; import org.jdrupes.vmoperator.manager.events.ModifyVm; import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jgrapes.core.Channel; @@ -37,7 +38,6 @@ import org.jgrapes.core.Component; import org.jgrapes.core.annotation.Handler; import org.jgrapes.core.events.HandlingError; import org.jgrapes.core.events.Start; -import org.jgrapes.core.events.Stop; import org.jgrapes.util.events.ConfigurationUpdate; /** @@ -146,7 +146,7 @@ public class Controller extends Component { logger.severe(() -> "Namespace to control not configured and" + " no file in kubernetes directory."); event.cancel(true); - fire(new Stop()); + fire(new Exit(2)); return; } logger.fine(() -> "Controlling namespace \"" + namespace + "\"."); diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java index 1175d8b..8c8e0fd 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java @@ -41,6 +41,7 @@ import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME; +import org.jdrupes.vmoperator.manager.events.Exit; import org.jdrupes.vmoperator.util.FsdUtils; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; @@ -82,6 +83,7 @@ public class Manager extends Component { private static Manager app; private String clusterName; private String namespace = "unknown"; + private static int exitStatus; /** * Instantiates a new manager. @@ -224,6 +226,16 @@ public class Manager extends Component { event.stop(); } + /** + * On exit. + * + * @param event the event + */ + @Handler + public void onExit(Exit event) { + exitStatus = event.exitStatus(); + } + /** * On stop. * @@ -294,6 +306,10 @@ public class Manager extends Component { // Start the application Components.start(app); + + // Wait for (regular) termination + Components.awaitExhaustion(); + System.exit(exitStatus); } catch (IOException | InterruptedException | org.apache.commons.cli.ParseException e) { Logger.getLogger(Manager.class.getName()).log(Level.SEVERE, e, diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java index 7f8b0c4..a6c2f97 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java @@ -52,6 +52,7 @@ import org.jdrupes.vmoperator.common.K8s; 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; +import org.jdrupes.vmoperator.manager.events.Exit; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type; @@ -105,7 +106,18 @@ public class VmWatcher extends Component { * @throws ApiException */ @Handler(priority = 10) - public void onStart(Start event) throws IOException, ApiException { + public void onStart(Start event) { + try { + startWatching(); + } catch (IOException | ApiException e) { + logger.log(Level.SEVERE, e, + () -> "Cannot watch VMs, terminating."); + event.cancel(true); + fire(new Exit(1)); + } + } + + private void startWatching() throws IOException, ApiException { // Get namespace if (namespaceToWatch == null) { var path = Path From 2d9d8de357964932944e027b050767a07e31d54d Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Fri, 12 Jan 2024 23:13:16 +0100 Subject: [PATCH 104/462] Add informative log message. --- .../src/org/jdrupes/vmoperator/manager/VmWatcher.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java index a6c2f97..2d99727 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java @@ -219,6 +219,8 @@ public class VmWatcher extends Component { "PMD.AvoidCatchingThrowable", "PMD.AvoidCatchingGenericException" }) var watcher = new Thread(() -> { try { + logger.info(() -> "Watching objects created from " + + crd.getName() + "." + VM_OP_GROUP + "/" + version); // Watch sometimes terminates without apparent reason. while (true) { Instant startedAt = Instant.now(); From 57edc3698025887d685b8714db50eec3aaf9717d Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 1 Feb 2024 23:16:25 +0100 Subject: [PATCH 105/462] Use names consistently. --- .../org/jdrupes/vmoperator/vmconlet/VmConlet-preview.ftl.html | 4 ++-- .../org/jdrupes/vmoperator/vmconlet/l10n_de.properties | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-preview.ftl.html b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-preview.ftl.html index 11cd882..0c6aa37 100644 --- a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-preview.ftl.html +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-preview.ftl.html @@ -30,11 +30,11 @@ - + - + diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties index d17bab6..1804b81 100644 --- a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties @@ -2,8 +2,6 @@ conletName = VM Anzeige VMsSummary = VMs (gestartet/gesamt) -Used\ CPUs = Verwendete CPUs -Used\ RAM = Verwendetes RAM Period = Zeitraum Last\ hour = Letzte Stunde Last\ day = Letzter Tag From 49370b507b6ba9f1b6885fe514250bc07de0e5e2 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 4 Feb 2024 22:03:02 +0100 Subject: [PATCH 106/462] More memory needed when handling a large number of VMs. --- org.jdrupes.vmoperator.manager/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index 9385a6c..63f54ec 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -39,7 +39,7 @@ dependencies { application { applicationName = 'vm-manager' - applicationDefaultJvmArgs = ['-Xmx64m', '-XX:+UseParallelGC', + applicationDefaultJvmArgs = ['-Xmx128m', '-XX:+UseParallelGC', '-Djava.util.logging.manager=org.jdrupes.vmoperator.util.LongLoggingManager' ] // Define the main class for the application. From b5622a459cd7178d1c7392fa5086870cc5f2464a Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 17 Feb 2024 14:26:26 +0100 Subject: [PATCH 107/462] Fix searching for executable. --- .../org/jdrupes/vmoperator/runner/qemu/CommandDefinition.java | 1 + 1 file changed, 1 insertion(+) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CommandDefinition.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CommandDefinition.java index fadc4a0..9057606 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CommandDefinition.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CommandDefinition.java @@ -42,6 +42,7 @@ class CommandDefinition { for (JsonNode path : jsonData.get("executable")) { if (Files.isExecutable(Path.of(path.asText()))) { command.add(path.asText()); + break; } } if (command.isEmpty()) { From 24f762d28cc889d845337553dbbf294263f4336f Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 17 Feb 2024 14:37:12 +0100 Subject: [PATCH 108/462] Add cloud-init support in runner. --- .../config-sample.yaml | 8 ++ .../vmoperator/runner/qemu/Configuration.java | 14 +++ .../runner/qemu/Containerfile.alpine | 2 +- .../vmoperator/runner/qemu/Containerfile.arch | 1 + .../vmoperator/runner/qemu/Runner.java | 97 ++++++++++++++++--- .../templates/Standard-VM-latest.ftl.yaml | 23 +++++ 6 files changed, 132 insertions(+), 13 deletions(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml b/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml index 461e79b..34c1511 100644 --- a/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml +++ b/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml @@ -27,6 +27,14 @@ # be set when starting the runner during development e.g. from the IDE. # "namespace": ... + # Defines data for generating a cloud-init ISO image that is + # attached to the VM. + # "cloudInit": + # "metaData": + # ... + # "userData": + # ... + # Define the VM (required) "vm": # The VM's name (required) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java index dda438b..ff60176 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java @@ -24,6 +24,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.PosixFilePermission; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.logging.Level; @@ -65,10 +66,23 @@ public class Configuration implements Dto { /** The firmware vars. */ public Path firmwareVars; + /** Optional cloud-init data. */ + public CloudInit cloudInit; + /** The vm. */ @SuppressWarnings("PMD.ShortVariable") public Vm vm; + /** + * Subsection "cloud-init". + */ + public static class CloudInit implements Dto { + @SuppressWarnings("PMD.UseConcurrentHashMap") + public Map metaData; + @SuppressWarnings("PMD.UseConcurrentHashMap") + public Map userData; + } + /** * Subsection "vm". */ diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine index def82ef..b87049e 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine @@ -2,7 +2,7 @@ FROM docker.io/alpine RUN apk update -RUN apk add qemu-system-x86_64 qemu-modules ovmf swtpm openjdk17 +RUN apk add qemu-system-x86_64 qemu-modules ovmf swtpm openjdk17 mtools RUN mkdir -p /etc/qemu && echo "allow all" > /etc/qemu/bridge.conf diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch index e3055c0..2ccb2f9 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch @@ -6,6 +6,7 @@ RUN pacman-key --init \ && pacman -Sy --noconfirm archlinux-keyring && pacman -Su --noconfirm \ && pacman -S --noconfirm which qemu-full virtiofsd \ edk2-ovmf swtpm iproute2 bridge-utils jre17-openjdk-headless \ + mtools \ && pacman -Scc --noconfirm # Remove all targets. diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index 979ac8e..fe3784f 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; import freemarker.core.ParseException; import freemarker.template.MalformedTemplateNameException; import freemarker.template.TemplateException; @@ -40,6 +41,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -178,9 +180,12 @@ import org.jgrapes.util.events.WatchFile; * */ @SuppressWarnings({ "PMD.ExcessiveImports", "PMD.AvoidPrintStackTrace", - "PMD.DataflowAnomalyAnalysis" }) + "PMD.DataflowAnomalyAnalysis", "PMD.TooManyMethods" }) public class Runner extends Component { + private static final String QEMU = "qemu"; + private static final String SWTPM = "swtpm"; + private static final String CLOUD_INIT_IMG = "cloudInitImg"; private static final String TEMPLATE_DIR = "/opt/" + APP_NAME.replace("-", "") + "/templates"; private static final String DEFAULT_TEMPLATE @@ -190,16 +195,29 @@ public class Runner extends Component { private static int exitStatus; private EventPipeline rep; - private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()); + private final ObjectMapper yamlMapper = new ObjectMapper(YAMLFactory + .builder().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER) + .build()); private final JsonNode defaults; @SuppressWarnings("PMD.UseConcurrentHashMap") private Configuration config = new Configuration(); private final freemarker.template.Configuration fmConfig; private CommandDefinition swtpmDefinition; + private CommandDefinition cloudInitImgDefinition; private CommandDefinition qemuDefinition; private final QemuMonitor qemuMonitor; private State state = State.INITIALIZING; + /** Preparatory actions for QEMU start */ + @SuppressWarnings("PMD.FieldNamingConventions") + private enum QemuPreps { + Config, + Tpm, + CloudInit + } + + private final Set qemuLatch = new HashSet<>(); + /** * Instantiates a new runner. * @@ -293,10 +311,14 @@ public class Runner extends Component { // Obtain more context data from template var tplData = dataFromTemplate(); - swtpmDefinition = Optional.ofNullable(tplData.get("swtpm")) - .map(d -> new CommandDefinition("swtpm", d)).orElse(null); - qemuDefinition = Optional.ofNullable(tplData.get("qemu")) - .map(d -> new CommandDefinition("qemu", d)).orElse(null); + swtpmDefinition = Optional.ofNullable(tplData.get(SWTPM)) + .map(d -> new CommandDefinition(SWTPM, d)).orElse(null); + qemuDefinition = Optional.ofNullable(tplData.get(QEMU)) + .map(d -> new CommandDefinition(QEMU, d)).orElse(null); + cloudInitImgDefinition + = Optional.ofNullable(tplData.get(CLOUD_INIT_IMG)) + .map(d -> new CommandDefinition(CLOUD_INIT_IMG, d)) + .orElse(null); // Forward some values to child components qemuMonitor.configure(config.monitorSocket, @@ -360,6 +382,7 @@ public class Runner extends Component { .map(Object::toString).orElse(null)); model.put("firmwareVars", Optional.ofNullable(config.firmwareVars) .map(Object::toString).orElse(null)); + model.put("cloudInit", config.cloudInit); model.put("vm", config.vm); if (Optional.ofNullable(config.vm.display) .map(d -> d.spice).map(s -> s.ticket).isPresent()) { @@ -430,12 +453,56 @@ public class Runner extends Component { state = State.STARTING; rep.fire(new RunnerStateChange(state, "RunnerStarted", "Runner has been started")); - // Start first process + // Start first process(es) + qemuLatch.add(QemuPreps.Config); if (config.vm.useTpm && swtpmDefinition != null) { startProcess(swtpmDefinition); - return; + qemuLatch.add(QemuPreps.Tpm); + } + if (config.cloudInit != null) { + generateCloudInitImg(); + qemuLatch.add(QemuPreps.CloudInit); + } + mayBeStartQemu(QemuPreps.Config); + } + + private void mayBeStartQemu(QemuPreps done) { + synchronized (qemuLatch) { + if (qemuLatch.isEmpty()) { + return; + } + qemuLatch.remove(done); + if (qemuLatch.isEmpty()) { + startProcess(qemuDefinition); + } + } + } + + private void generateCloudInitImg() { + try { + var cloudInitDir = config.dataDir.resolve("cloud-init"); + cloudInitDir.toFile().mkdir(); + var metaOut + = Files.newBufferedWriter(cloudInitDir.resolve("meta-data")); + if (config.cloudInit.metaData != null) { + yamlMapper.writer().writeValue(metaOut, + config.cloudInit.metaData); + } + metaOut.close(); + var userOut + = Files.newBufferedWriter(cloudInitDir.resolve("user-data")); + userOut.write("#cloud-config\n"); + if (config.cloudInit.userData != null) { + yamlMapper.writer().writeValue(userOut, + config.cloudInit.userData); + } + userOut.close(); + startProcess(cloudInitImgDefinition); + } catch (IOException e) { + logger.log(Level.SEVERE, e, + () -> "Cannot start runner: " + e.getMessage()); + fire(new Stop()); } - startProcess(qemuDefinition); } private boolean startProcess(CommandDefinition toStart) { @@ -456,8 +523,8 @@ public class Runner extends Component { public void onFileChanged(FileChanged event) { if (event.change() == Kind.CREATED && event.path().equals(config.swtpmSocket)) { - // swtpm running, start qemu - startProcess(qemuDefinition); + // swtpm running, maybe start qemu + mayBeStartQemu(QemuPreps.Tpm); return; } } @@ -545,7 +612,13 @@ public class Runner extends Component { @Handler public void onProcessExited(ProcessExited event, ProcessChannel channel) { channel.associated(CommandDefinition.class).ifPresent(procDef -> { - // No process(es) may exit during startup + if (procDef.equals(cloudInitImgDefinition) + && event.exitValue() == 0) { + // Cloud-init ISO generation was successful. + mayBeStartQemu(QemuPreps.CloudInit); + return; + } + // No other process(es) may exit during startup if (state == State.STARTING) { logger.severe(() -> "Process " + procDef.name + " has exited with value " + event.exitValue() diff --git a/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml b/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml index b21db8f..dff656a 100644 --- a/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml +++ b/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml @@ -11,6 +11,19 @@ - [ "--ctrl", "type=unixio,path=${ runtimeDir }/swtpm-sock,mode=0600" ] - "--terminate" +"cloudInitImg": + # Candidate paths for the executable + "executable": [ "/bin/sh", "/usr/bin/sh" ] + + # Arguments may be specified as nested lists for better readability. + # The arguments are flattened before being passed to the process. + "arguments": + - "-c" + - >- + mformat -C -f 1440 -v CIDATA -i ${ runtimeDir }/cloud-init.img + && mcopy -i ${ runtimeDir }/cloud-init.img + ${ dataDir }/cloud-init/meta-data ${ dataDir }/cloud-init/user-data :: + "qemu": # Candidate paths for the executable "executable": [ "/usr/bin/qemu-system-x86_64" ] @@ -183,6 +196,16 @@ <#break> + # Cloud-init image + <#if cloudInit??> + - [ "-blockdev", "node-name=drive-${ drvCounter }-host-resource,\ + driver=file,filename=${ runtimeDir }/cloud-init.img" ] + # - how to use the file (as sequence of literal blocks) + - [ "-blockdev", "node-name=drive-${ drvCounter }-backend,driver=raw,\ + file=drive-${ drvCounter }-host-resource" ] + # - the driver (what the guest sees) + - [ "-device", "virtio-blk-pci,drive=drive-${ drvCounter }-backend" ] + <#if vm.display??> <#if vm.display.spice??> From 599f64da4cac76e0316a80260bb904551219378d Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 17 Feb 2024 16:47:10 +0100 Subject: [PATCH 109/462] Provide fallback for instance-id. --- .../config-sample.yaml | 5 +++- .../vmoperator/runner/qemu/Configuration.java | 22 ++++++++++++++++ .../vmoperator/runner/qemu/Runner.java | 26 +++++++++---------- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml b/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml index 34c1511..2211fed 100644 --- a/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml +++ b/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml @@ -33,7 +33,10 @@ # "metaData": # ... # "userData": - # ... + # ... + # + # If .metaData.instance-id is missing, an id is generated from the + # config file's modification timestamp. # Define the VM (required) "vm": diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java index ff60176..ba36317 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java @@ -24,6 +24,8 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.PosixFilePermission; +import java.time.Instant; +import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -39,9 +41,14 @@ import org.jdrupes.vmoperator.util.FsdUtils; */ @SuppressWarnings("PMD.ExcessivePublicCount") public class Configuration implements Dto { + private static final String CI_INSTANCE_ID = "instance-id"; + @SuppressWarnings("PMD.FieldNamingConventions") protected final Logger logger = Logger.getLogger(getClass().getName()); + /** Configuration timestamp */ + public Instant asOf; + /** The data dir. */ public Path dataDir; @@ -259,6 +266,7 @@ public class Configuration implements Dto { } checkDrives(); + checkCloudInit(); return true; } @@ -372,4 +380,18 @@ public class Configuration implements Dto { return true; } + + private void checkCloudInit() { + if (cloudInit == null) { + return; + } + + // Provide default for instance-id + if (cloudInit.metaData == null) { + cloudInit.metaData = new HashMap<>(); + } + if (!cloudInit.metaData.containsKey(CI_INSTANCE_ID)) { + cloudInit.metaData.put(CI_INSTANCE_ID, "v" + asOf.getEpochSecond()); + } + } } \ No newline at end of file diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index fe3784f..fccdf89 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -39,10 +39,10 @@ import java.lang.reflect.UndeclaredThrowableException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Instant; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.logging.Level; @@ -200,6 +200,7 @@ public class Runner extends Component { .build()); private final JsonNode defaults; @SuppressWarnings("PMD.UseConcurrentHashMap") + private final File configFile; private Configuration config = new Configuration(); private final freemarker.template.Configuration fmConfig; private CommandDefinition swtpmDefinition; @@ -252,16 +253,16 @@ public class Runner extends Component { attach(qemuMonitor = new QemuMonitor(channel())); attach(new StatusUpdater(channel())); - // Configuration store with file in /etc/opt (default) - File config = new File(cmdLine.getOptionValue('c', + configFile = new File(cmdLine.getOptionValue('c', "/etc/opt/" + APP_NAME.replace("-", "") + "/config.yaml")); // Don't rely on night config to produce a good exception // for this simple case - if (!Files.isReadable(config.toPath())) { - throw new IOException("Cannot read configuration file " + config); + if (!Files.isReadable(configFile.toPath())) { + throw new IOException( + "Cannot read configuration file " + configFile); } - attach(new YamlConfigurationStore(channel(), config, false)); - fire(new WatchFile(config.toPath())); + attach(new YamlConfigurationStore(channel(), configFile, false)); + fire(new WatchFile(configFile.toPath())); } /** @@ -286,21 +287,20 @@ public class Runner extends Component { @Handler public void onConfigurationUpdate(ConfigurationUpdate event) { event.structured(componentPath()).ifPresent(c -> { + var newConf = yamlMapper.convertValue(c, Configuration.class); + newConf.asOf = Instant.ofEpochSecond(configFile.lastModified()); if (event instanceof InitialConfiguration) { - processInitialConfiguration(c); + processInitialConfiguration(newConf); return; } logger.fine(() -> "Updating configuration"); - var newConf = yamlMapper.convertValue(c, Configuration.class); rep.fire(new RunnerConfigurationUpdate(newConf, state)); }); } - private void processInitialConfiguration( - Map runnerConfiguration) { + private void processInitialConfiguration(Configuration newConfig) { try { - config = yamlMapper.convertValue(runnerConfiguration, - Configuration.class); + config = newConfig; if (!config.check()) { // Invalid configuration, not used, problems already logged. config = null; From 542c4eb61f3a2e1907e6dfc79b4350c723f17973 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 17 Feb 2024 17:50:03 +0100 Subject: [PATCH 110/462] Update diagram. --- .../vmoperator/runner/qemu/Runner.java | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index fccdf89..5df2d4b 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -112,21 +112,27 @@ import org.jgrapes.util.events.WatchFile; * * state "Starting (Processes)" as StartingProcess { * - * state which <> - * state "Start swtpm" as swtpm * state "Start qemu" as qemu * state "Open monitor" as monitor * state "Configure QMP" as waitForConfigured * state "Configure QEMU" as configure * state success <> * state error <> - * - * which --> swtpm: [use swtpm] - * which --> qemu: [else] - * + * + * state prepFork <> + * state prepJoin <> + * state "Generate cloud-init image" as cloudInit + * prepFork --> cloudInit: [cloud-init data provided] + * swtpm --> prepJoin: FileChanged[swtpm socket created] + * state "Start swtpm" as swtpm + * prepFork --> swtpm: [use swtpm] * swtpm: entry/start swtpm - * swtpm -> qemu: FileChanged[swtpm socket created] - * + * cloudInit --> prepJoin: ProcessExited + * cloudInit: entry/generate cloud-init image + * prepFork --> prepJoin: [else] + * + * prepJoin --> qemu + * * qemu: entry/start qemu * qemu --> monitor : FileChanged[monitor socket created] * @@ -141,7 +147,7 @@ import org.jgrapes.util.events.WatchFile; * configure --> success: RunnerConfigurationUpdate (last handler)/fire cont command * } * - * Initializing --> which: Started + * Initializing --> prepFork: Started * * success --> Running * From 499c1822fd003009f0f911d0fa49760982f88a11 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 17 Feb 2024 19:42:44 +0100 Subject: [PATCH 111/462] Do push when testing. --- org.jdrupes.vmoperator.manager/build.gradle | 6 ++++++ org.jdrupes.vmoperator.runner.qemu/build.gradle | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index 63f54ec..38e43f1 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -60,6 +60,9 @@ task tagLatestImage(type: Exec) { enabled = !project.version.contains("SNAPSHOT") && !project.version.contains("alpha") \ && !project.version.contains("beta") \ + || project.rootProject.properties['docker.testRegistry'] \ + && project.rootProject.properties['docker.registry'] \ + == project.rootProject.properties['docker.testRegistry'] commandLine 'podman', 'tag', "${project.name}:${project.version}",\ "${project.name}:latest" @@ -85,6 +88,9 @@ task pushLatestImage(type: Exec) { enabled = !project.version.contains("SNAPSHOT") && !project.version.contains("alpha") \ && !project.version.contains("beta") \ + || project.rootProject.properties['docker.testRegistry'] \ + && project.rootProject.properties['docker.registry'] \ + == project.rootProject.properties['docker.testRegistry'] commandLine 'podman', 'push', '--tls-verify=false', \ "localhost/${project.name}:${project.version}", \ diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle index ec7de7f..3a5a4b7 100644 --- a/org.jdrupes.vmoperator.runner.qemu/build.gradle +++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle @@ -45,6 +45,9 @@ task tagLatestArchImage(type: Exec) { enabled = !project.version.contains("SNAPSHOT") && !project.version.contains("alpha") \ && !project.version.contains("beta") \ + || project.rootProject.properties['docker.testRegistry'] \ + && project.rootProject.properties['docker.registry'] \ + == project.rootProject.properties['docker.testRegistry'] commandLine 'podman', 'tag', "${project.name}-arch:${project.version}",\ "${project.name}-arch:latest" @@ -70,6 +73,9 @@ task pushArchLatestImage(type: Exec) { enabled = !project.version.contains("SNAPSHOT") && !project.version.contains("alpha") \ && !project.version.contains("beta") \ + || project.rootProject.properties['docker.testRegistry'] \ + && project.rootProject.properties['docker.registry'] \ + == project.rootProject.properties['docker.testRegistry'] commandLine 'podman', 'push', '--tls-verify=false', \ "localhost/${project.name}-arch:${project.version}", \ @@ -91,6 +97,9 @@ task tagLatestAlpineImage(type: Exec) { enabled = !project.version.contains("SNAPSHOT") && !project.version.contains("alpha") \ && !project.version.contains("beta") \ + || project.rootProject.properties['docker.testRegistry'] \ + && project.rootProject.properties['docker.registry'] \ + == project.rootProject.properties['docker.testRegistry'] commandLine 'podman', 'tag', "${project.name}-alpine:${project.version}",\ "${project.name}-alpine:latest" @@ -116,6 +125,9 @@ task pushAlpineLatestImage(type: Exec) { enabled = !project.version.contains("SNAPSHOT") && !project.version.contains("alpha") \ && !project.version.contains("beta") \ + || project.rootProject.properties['docker.testRegistry'] \ + && project.rootProject.properties['docker.registry'] \ + == project.rootProject.properties['docker.testRegistry'] commandLine 'podman', 'push', '--tls-verify=false', \ "localhost/${project.name}-alpine:${project.version}", \ From b255f0e9468f1744c239db799bfb5d2f193e8866 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 18 Feb 2024 11:46:09 +0100 Subject: [PATCH 112/462] Support cloudInit in CRD. --- deploy/crds/vms-crd.yaml | 15 ++++++++++ dev-example/.gitignore | 1 + dev-example/test-vm-shell.yaml | 30 +++++++++++++++++++ .../vmoperator/manager/runnerConfig.ftl.yaml | 17 ++++++++++- 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 dev-example/.gitignore create mode 100644 dev-example/test-vm-shell.yaml diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index 7e321da..9de714b 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -965,6 +965,21 @@ spec: additionalProperties: type: string nullable: true + cloudInit: + type: object + description: >- + Provides data for generating a cloud-init ISO + image that is attached to the VM. + properties: + metaData: + description: Copied to cloud-init's meta-data file. + type: object + additionalProperties: + type: string + userData: + description: Copied to cloud-init's user-data file. + type: object + x-kubernetes-preserve-unknown-fields: true vm: type: object description: Defines the VM. diff --git a/dev-example/.gitignore b/dev-example/.gitignore new file mode 100644 index 0000000..925478d --- /dev/null +++ b/dev-example/.gitignore @@ -0,0 +1 @@ +/test-vm-ci.yaml diff --git a/dev-example/test-vm-shell.yaml b/dev-example/test-vm-shell.yaml new file mode 100644 index 0000000..8137694 --- /dev/null +++ b/dev-example/test-vm-shell.yaml @@ -0,0 +1,30 @@ +kind: Pod +apiVersion: v1 +metadata: + name: test-vm-shell + namespace: vmop-dev +spec: + volumes: + - name: test-vm-system-disk + persistentVolumeClaim: + claimName: system-disk-test-vm-0 + - name: vmop-image-repository + persistentVolumeClaim: + claimName: vmop-image-repository + containers: + - name: test-vm-shell + image: archlinux/archlinux + args: + - bash + imagePullPolicy: Always + stdin: true + stdinOnce: true + tty: true + volumeDevices: + - name: test-vm-system-disk + devicePath: /dev/test-vm-system-disk + volumeMounts: + - name: vmop-image-repository + mountPath: /var/local/vmop-image-repository + securityContext: + privileged: true diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml index d49e705..64c5cbf 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml @@ -44,7 +44,22 @@ data: <#if cr.spec.runnerTemplate?? && cr.spec.runnerTemplate.update?? > updateTemplate: ${ cr.spec.runnerTemplate.update.asBoolean?c } - + + # Forward the cloud-init data if provided + <#if cr.spec.cloudInit??> + cloudInit: + <#if cr.spec.cloudInit.metaData??> + metaData: ${ cr.spec.cloudInit.metaData.toString() } + <#else> + metaData: {} + + <#if cr.spec.cloudInit.userData??> + userData: ${ cr.spec.cloudInit.userData.toString() } + <#else> + userData: {} + + + # Define the VM (required) vm: # The VM's name (required) From 7835686304aa504611e1a491f5df9c2406221a26 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 18 Feb 2024 13:17:51 +0100 Subject: [PATCH 113/462] Javadoc fix. --- .../src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java index cc17295..c7f634b 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java @@ -36,7 +36,7 @@ public class TimeSeries { /** * Instantiates a new time series. * - * @param series the number of series + * @param period the period */ public TimeSeries(Duration period) { this.period = period; From ee96f869daebbbf95ec5fcfb26ea8cb4553944ed Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 18 Feb 2024 13:35:31 +0100 Subject: [PATCH 114/462] Add generation of fallback properties. --- dev-example/test-vm.yaml | 2 ++ .../vmoperator/manager/Reconciler.java | 28 +++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/dev-example/test-vm.yaml b/dev-example/test-vm.yaml index 0cd820b..5913020 100644 --- a/dev-example/test-vm.yaml +++ b/dev-example/test-vm.yaml @@ -14,6 +14,8 @@ spec: cpu: 1 memory: 2Gi + cloudInit: {} + vm: # state: Running bootMenu: yes diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index c3fee7d..2adb843 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -209,6 +209,15 @@ public class Reconciler extends Component { private DynamicKubernetesObject patchCr(DynamicKubernetesObject vmDef) { var json = vmDef.getRaw().deepCopy(); // Adjust cdromImage path + adjustCdRomPaths(json); + + // Adjust cloud-init data + adjustCloudInitData(json); + + return new DynamicKubernetesObject(json); + } + + private void adjustCdRomPaths(JsonObject json) { var disks = GsonPtr.to(json).to("spec", "vm", "disks").get(JsonArray.class); for (var disk : disks) { @@ -233,7 +242,23 @@ public class Reconciler extends Component { logger.warning(() -> "Invalid CDROM image: " + image); } } - return new DynamicKubernetesObject(json); + } + + private void adjustCloudInitData(JsonObject json) { + var spec = GsonPtr.to(json).to("spec").get(JsonObject.class); + if (!spec.has("cloudInit")) { + return; + } + var metaData = GsonPtr.to(spec).to("cloudInit", "metaData"); + if (metaData.getAsString("instance-id").isEmpty()) { + metaData.set("instance-id", + GsonPtr.to(json).getAsString("metadata", "resourceVersion") + .map(s -> "v" + s).orElse("v1")); + } + if (metaData.getAsString("local-hostname").isEmpty()) { + metaData.set("local-hostname", + GsonPtr.to(json).getAsString("metadata", "name").get()); + } } @SuppressWarnings("PMD.CognitiveComplexity") @@ -300,5 +325,4 @@ public class Reconciler extends Component { }); return model; } - } From eccc35d0bfc56da43cb10b6117214d5afc8ab5fe Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 20 Feb 2024 21:51:48 +0100 Subject: [PATCH 115/462] Make sure to get latest base image versions. --- org.jdrupes.vmoperator.manager/build.gradle | 3 ++- org.jdrupes.vmoperator.runner.qemu/build.gradle | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index 38e43f1..d403be8 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -50,7 +50,8 @@ task buildImage(type: Exec) { dependsOn installDist inputs.files 'src/org/jdrupes/vmoperator/manager/Containerfile' - commandLine 'podman', 'build', '-t', "${project.name}:${project.version}",\ + commandLine 'podman', 'build', '--pull', + '-t', "${project.name}:${project.version}",\ '-f', 'src/org/jdrupes/vmoperator/manager/Containerfile', '.' } diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle index 3a5a4b7..7179b8f 100644 --- a/org.jdrupes.vmoperator.runner.qemu/build.gradle +++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle @@ -35,7 +35,8 @@ task buildArchImage(type: Exec) { dependsOn installDist inputs.files 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch' - commandLine 'podman', 'build', '-t', "${project.name}-arch:${project.version}",\ + commandLine 'podman', 'build', '--pull', + '-t', "${project.name}-arch:${project.version}",\ '-f', 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch', '.' } @@ -87,7 +88,8 @@ task buildAlpineImage(type: Exec) { dependsOn installDist inputs.files 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine' - commandLine 'podman', 'build', '-t', "${project.name}-alpine:${project.version}",\ + commandLine 'podman', 'build', '--pull', + '-t', "${project.name}-alpine:${project.version}",\ '-f', 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine', '.' } From 413f8f76dc34f2eaaa641db93df92afd97948d73 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Wed, 21 Feb 2024 16:14:37 +0100 Subject: [PATCH 116/462] Support cloud-init's network-config. --- org.jdrupes.vmoperator.runner.qemu/config-sample.yaml | 5 ++++- .../org/jdrupes/vmoperator/runner/qemu/Configuration.java | 2 ++ .../src/org/jdrupes/vmoperator/runner/qemu/Runner.java | 7 +++++++ .../templates/Standard-VM-latest.ftl.yaml | 3 +++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml b/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml index 2211fed..4dc87a2 100644 --- a/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml +++ b/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml @@ -34,9 +34,12 @@ # ... # "userData": # ... + # "networkConfig": + # ... # # If .metaData.instance-id is missing, an id is generated from the - # config file's modification timestamp. + # config file's modification timestamp. .userData and .networkConfig + # are optional. # Define the VM (required) "vm": diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java index ba36317..192b44c 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java @@ -88,6 +88,8 @@ public class Configuration implements Dto { public Map metaData; @SuppressWarnings("PMD.UseConcurrentHashMap") public Map userData; + @SuppressWarnings("PMD.UseConcurrentHashMap") + public Map networkConfig; } /** diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index 5df2d4b..d615ad6 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -503,6 +503,13 @@ public class Runner extends Component { config.cloudInit.userData); } userOut.close(); + if (config.cloudInit.networkConfig != null) { + var networkConfig = Files.newBufferedWriter( + cloudInitDir.resolve("network-config")); + yamlMapper.writer().writeValue(networkConfig, + config.cloudInit.networkConfig); + networkConfig.close(); + } startProcess(cloudInitImgDefinition); } catch (IOException e) { logger.log(Level.SEVERE, e, diff --git a/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml b/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml index dff656a..aa7f49e 100644 --- a/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml +++ b/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml @@ -23,6 +23,9 @@ mformat -C -f 1440 -v CIDATA -i ${ runtimeDir }/cloud-init.img && mcopy -i ${ runtimeDir }/cloud-init.img ${ dataDir }/cloud-init/meta-data ${ dataDir }/cloud-init/user-data :: + && if [ -r ${ dataDir }/cloud-init/network-config ]; then + mcopy -i ${ runtimeDir }/cloud-init.img + ${ dataDir }/cloud-init/network-config :: ; fi "qemu": # Candidate paths for the executable From 639e3157698c4dae42293c7b2fb7eafa7b69ac6e Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Wed, 21 Feb 2024 22:21:32 +0100 Subject: [PATCH 117/462] Add network-config to CRD. --- deploy/crds/vms-crd.yaml | 4 ++++ .../org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml | 3 +++ 2 files changed, 7 insertions(+) diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index 9de714b..017050f 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -980,6 +980,10 @@ spec: description: Copied to cloud-init's user-data file. type: object x-kubernetes-preserve-unknown-fields: true + networkConfig: + description: Copied to cloud-init's network-config file. + type: object + x-kubernetes-preserve-unknown-fields: true vm: type: object description: Defines the VM. diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml index 64c5cbf..75371cc 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml @@ -58,6 +58,9 @@ data: <#else> userData: {} + <#if cr.spec.cloudInit.networkConfig??> + networkConfig: ${ cr.spec.cloudInit.networkConfig.toString() } + # Define the VM (required) From 48f86a6ef2c11f7f4d5fab0ab75e9c123ddc2493 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 22 Feb 2024 09:59:51 +0100 Subject: [PATCH 118/462] Use commonly used prefix. --- dev-example/test-vm.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-example/test-vm.yaml b/dev-example/test-vm.yaml index 5913020..1959e4e 100644 --- a/dev-example/test-vm.yaml +++ b/dev-example/test-vm.yaml @@ -27,7 +27,7 @@ spec: networks: - tap: - mac: "00:16:3e:33:58:10" + mac: "00:50:56:33:58:10" disks: - volumeClaimTemplate: metadata: From 1a608df411cad973fe0c9f289bb79165e6fbab91 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 22 Feb 2024 10:13:14 +0100 Subject: [PATCH 119/462] Use locally administered address in example. --- dev-example/test-vm.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-example/test-vm.yaml b/dev-example/test-vm.yaml index 1959e4e..dcb3454 100644 --- a/dev-example/test-vm.yaml +++ b/dev-example/test-vm.yaml @@ -27,7 +27,7 @@ spec: networks: - tap: - mac: "00:50:56:33:58:10" + mac: "02:16:3e:33:58:10" disks: - volumeClaimTemplate: metadata: From aa7fdbee080d7ffb626b18f68057c74d4082c2b2 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 25 Feb 2024 13:48:44 +0100 Subject: [PATCH 120/462] Allow guest to (finally) shutdown the VM. --- deploy/vmrunner-role.yaml | 1 + .../vmoperator/runner/qemu/Runner.java | 26 +++++----- .../vmoperator/runner/qemu/StatusUpdater.java | 31 ++++++++++++ .../runner/qemu/events/MonitorEvent.java | 17 ++++--- .../runner/qemu/events/RunnerStateChange.java | 32 +++++++++++++ .../runner/qemu/events/ShutdownEvent.java | 47 +++++++++++++++++++ 6 files changed, 136 insertions(+), 18 deletions(-) create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ShutdownEvent.java diff --git a/deploy/vmrunner-role.yaml b/deploy/vmrunner-role.yaml index 8aea4e2..c6df666 100644 --- a/deploy/vmrunner-role.yaml +++ b/deploy/vmrunner-role.yaml @@ -12,6 +12,7 @@ rules: verbs: - list - get + - patch - apiGroups: - vmoperator.jdrupes.org resources: diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index d615ad6..922f2af 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -640,13 +640,25 @@ public class Runner extends Component { return; } if (procDef.equals(qemuDefinition) && state == State.RUNNING) { - rep.fire(new Stop()); + rep.fire(new Exit(event.exitValue())); } logger.info(() -> "Process " + procDef.name + " has exited with value " + event.exitValue()); }); } + /** + * On exit. + * + * @param event the event + */ + @Handler(priority = 10_001) + public void onExit(Exit event) { + if (exitStatus == 0) { + exitStatus = event.exitStatus(); + } + } + /** * On stop. * @@ -656,7 +668,7 @@ public class Runner extends Component { public void onStopFirst(Stop event) { state = State.TERMINATING; rep.fire(new RunnerStateChange(state, "VmTerminating", - "The VM is being shut down")); + "The VM is being shut down", exitStatus != 0)); } /** @@ -671,16 +683,6 @@ public class Runner extends Component { "The VM has been shut down")); } - /** - * On exit. - * - * @param event the event - */ - @Handler - public void onExit(Exit event) { - exitStatus = event.exitStatus(); - } - private void shutdown() { if (!Set.of(State.TERMINATING, State.STOPPED).contains(state)) { fire(new Stop()); diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index 542fa06..5f8cf13 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -21,6 +21,7 @@ package org.jdrupes.vmoperator.runner.qemu; import com.google.gson.JsonObject; import io.kubernetes.client.custom.Quantity; import io.kubernetes.client.custom.Quantity.Format; +import io.kubernetes.client.custom.V1Patch; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.ApisApi; import io.kubernetes.client.openapi.apis.CustomObjectsApi; @@ -32,6 +33,7 @@ import io.kubernetes.client.openapi.models.V1ObjectMeta; import io.kubernetes.client.util.Config; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; +import io.kubernetes.client.util.generic.options.PatchOptions; import java.io.IOException; import java.math.BigDecimal; import java.nio.file.Files; @@ -52,6 +54,7 @@ import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus; import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; +import org.jdrupes.vmoperator.runner.qemu.events.ShutdownEvent; import org.jdrupes.vmoperator.util.GsonPtr; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; @@ -75,6 +78,7 @@ public class StatusUpdater extends Component { private DynamicKubernetesApi vmCrApi; private EventsV1Api evtsApi; private long observedGeneration; + private boolean shutdownByGuest; /** * Instantiates a new status updater. @@ -268,6 +272,22 @@ public class StatusUpdater extends Component { return status; }).throwsApiException(); + // Maybe stop VM + if (event.state() == State.TERMINATING && !event.failed() + && shutdownByGuest) { + PatchOptions patchOpts = new PatchOptions(); + patchOpts.setFieldManager("kubernetes-java-kubectl-apply"); + var res = vmCrApi.patch(namespace, vmName, + V1Patch.PATCH_FORMAT_JSON_PATCH, + new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/state" + + "\", \"value\": \"Stopped\"}]"), + patchOpts); + if (!res.isSuccess()) { + logger.warning( + () -> "Cannot patch pod annotations: " + res.getStatus()); + } + } + // Log event var evt = new EventsV1Event().kind("Event") .metadata(new V1ObjectMeta().namespace(namespace) @@ -344,4 +364,15 @@ public class StatusUpdater extends Component { return status; }).throwsApiException(); } + + /** + * On shutdown. + * + * @param event the event + * @throws ApiException the api exception + */ + @Handler + public void onShutdown(ShutdownEvent event) throws ApiException { + shutdownByGuest = event.byGuest(); + } } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java index 72647a1..ba04a26 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorEvent.java @@ -28,11 +28,13 @@ import org.jgrapes.core.Event; */ public class MonitorEvent extends Event { + private static final String EVENT_DATA = "data"; + /** * The kind of monitor event. */ public enum Kind { - READY, POWERDOWN, DEVICE_TRAY_MOVED, BALLOON_CHANGE + READY, POWERDOWN, DEVICE_TRAY_MOVED, BALLOON_CHANGE, SHUTDOWN } private final Kind kind; @@ -47,20 +49,23 @@ public class MonitorEvent extends Event { @SuppressWarnings("PMD.TooFewBranchesForASwitchStatement") public static Optional from(JsonNode response) { try { - var kind - = MonitorEvent.Kind.valueOf(response.get("event").asText()); + var kind = MonitorEvent.Kind + .valueOf(response.get("event").asText()); switch (kind) { case POWERDOWN: return Optional.of(new PowerdownEvent(kind, null)); case DEVICE_TRAY_MOVED: return Optional - .of(new TrayMovedEvent(kind, response.get("data"))); + .of(new TrayMovedEvent(kind, response.get(EVENT_DATA))); case BALLOON_CHANGE: + return Optional.of( + new BalloonChangeEvent(kind, response.get(EVENT_DATA))); + case SHUTDOWN: return Optional - .of(new BalloonChangeEvent(kind, response.get("data"))); + .of(new ShutdownEvent(kind, response.get(EVENT_DATA))); default: return Optional - .of(new MonitorEvent(kind, response.get("data"))); + .of(new MonitorEvent(kind, response.get(EVENT_DATA))); } } catch (IllegalArgumentException e) { return Optional.empty(); diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java index 46fa1f8..5d5bffd 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java @@ -25,6 +25,7 @@ import org.jgrapes.core.Event; /** * The Class RunnerStateChange. */ +@SuppressWarnings("PMD.DataClass") public class RunnerStateChange extends Event { /** @@ -37,17 +38,36 @@ public class RunnerStateChange extends Event { private final State state; private final String reason; private final String message; + private final boolean failed; /** * Instantiates a new runner state change. * + * @param state the state + * @param reason the reason + * @param message the message * @param channels the channels */ public RunnerStateChange(State state, String reason, String message, Channel... channels) { + this(state, reason, message, false, channels); + } + + /** + * Instantiates a new runner state change. + * + * @param state the state + * @param reason the reason + * @param message the message + * @param failed the failed + * @param channels the channels + */ + public RunnerStateChange(State state, String reason, String message, + boolean failed, Channel... channels) { super(channels); this.state = state; this.reason = reason; + this.failed = failed; this.message = message; } @@ -78,11 +98,23 @@ public class RunnerStateChange extends Event { return message; } + /** + * Checks if is failed. + * + * @return the failed + */ + public boolean failed() { + return failed; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(Components.objectName(this)) .append(" [").append(state).append(": ").append(reason); + if (failed) { + builder.append(" (failed)"); + } if (channels() != null) { builder.append(", channels="); builder.append(Channel.toString(channels())); diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ShutdownEvent.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ShutdownEvent.java new file mode 100644 index 0000000..e46bbd3 --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ShutdownEvent.java @@ -0,0 +1,47 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.runner.qemu.events; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Signals the processing of the {@link QmpShutdown} event. + */ +public class ShutdownEvent extends MonitorEvent { + + /** + * Instantiates a new shutdown event. + * + * @param kind the kind + * @param data the data + */ + public ShutdownEvent(Kind kind, JsonNode data) { + super(kind, data); + } + + /** + * returns if this is initiated by the guest. + * + * @return the value + */ + public boolean byGuest() { + return data().get("guest").asBoolean(); + } + +} From bbe2d6efbc1bbfe3b7814c72571bb87f0439b606 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 25 Feb 2024 14:27:54 +0100 Subject: [PATCH 121/462] Make this explicit (implied by replicas = 1). --- .../resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml index ac1178a..2e5712b 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml @@ -23,6 +23,8 @@ spec: 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) } + updateStrategy: + type: OnDelete template: metadata: namespace: ${ cr.metadata.namespace.asString } From fb4a0206f162a7436b081c1b65d5d00f2f6cacb4 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 25 Feb 2024 15:49:56 +0100 Subject: [PATCH 122/462] Make result of guest shutdown configurable. --- deploy/crds/vms-crd.yaml | 6 ++++++ .../org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml | 2 ++ org.jdrupes.vmoperator.runner.qemu/config-sample.yaml | 5 +++++ .../org/jdrupes/vmoperator/runner/qemu/Configuration.java | 3 +++ .../org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java | 7 ++++++- 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index 017050f..0292b8d 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -999,6 +999,12 @@ spec: type: string enum: [ "Stopped", "Running" ] default: "Stopped" + guestShutdownStops: + description: >- + If true, sets the state to "Stopped" when + the VM terminates due to a shutdown by the guest. + type: boolean + default: false machineUuid: description: >- The machine's uuid. If none is specified, a uuid diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml index 75371cc..d96a66b 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml @@ -63,6 +63,8 @@ data: + guestShutdownStops: ${ cr.spec.guestShutdownStops!false?string('true', 'false') } + # Define the VM (required) vm: # The VM's name (required) diff --git a/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml b/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml index 4dc87a2..c365a12 100644 --- a/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml +++ b/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml @@ -41,6 +41,11 @@ # config file's modification timestamp. .userData and .networkConfig # are optional. + # Whether a guest initiated shutdown event patches the state + # property in the CRD. + # "guestShutdownStops": + # false + # Define the VM (required) "vm": # The VM's name (required) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java index 192b44c..7fc3f95 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java @@ -76,6 +76,9 @@ public class Configuration implements Dto { /** Optional cloud-init data. */ public CloudInit cloudInit; + /** If guest shutdown changes CRD .vm.state to "Stopped". */ + public boolean guestShutdownStops; + /** The vm. */ @SuppressWarnings("PMD.ShortVariable") public Vm vm; diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index 5f8cf13..1cb5e74 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -78,6 +78,7 @@ public class StatusUpdater extends Component { private DynamicKubernetesApi vmCrApi; private EventsV1Api evtsApi; private long observedGeneration; + private boolean guestShutdownStops; private boolean shutdownByGuest; /** @@ -217,6 +218,9 @@ public class StatusUpdater extends Component { @Handler public void onRunnerConfigurationUpdate(RunnerConfigurationUpdate event) throws ApiException { + guestShutdownStops = event.configuration().guestShutdownStops; + + // Remainder applies only if we have a connection to k8s. if (vmCrApi == null) { return; } @@ -274,7 +278,8 @@ public class StatusUpdater extends Component { // Maybe stop VM if (event.state() == State.TERMINATING && !event.failed() - && shutdownByGuest) { + && guestShutdownStops && shutdownByGuest) { + logger.info(() -> "Stopping VM because of shutdown by guest."); PatchOptions patchOpts = new PatchOptions(); patchOpts.setFieldManager("kubernetes-java-kubectl-apply"); var res = vmCrApi.patch(namespace, vmName, From b207e0226fa7b49b27e1fb370cca1c7b6eab0298 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 25 Feb 2024 17:22:06 +0100 Subject: [PATCH 123/462] Fix template error. --- .../org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml index d96a66b..64822a5 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml @@ -63,7 +63,7 @@ data: - guestShutdownStops: ${ cr.spec.guestShutdownStops!false?string('true', 'false') } + guestShutdownStops: ${ cr.spec.vm.guestShutdownStops!false?c } # Define the VM (required) vm: From e25358085fac0fb44c4d67651380138711c401b4 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 26 Feb 2024 15:15:55 +0100 Subject: [PATCH 124/462] Move guestShutdownStops up one level. --- deploy/crds/vms-crd.yaml | 12 ++++++------ dev-example/test-vm.yaml | 4 +++- .../jdrupes/vmoperator/manager/runnerConfig.ftl.yaml | 5 +++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index 0292b8d..1863afe 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -933,6 +933,12 @@ spec: update: type: boolean default: true + guestShutdownStops: + description: >- + If true, sets the VM's state to "Stopped" when + the VM terminates due to a shutdown by the guest. + type: boolean + default: false loadBalancerService: description: >- Data to be merged with the loadBalancerService @@ -999,12 +1005,6 @@ spec: type: string enum: [ "Stopped", "Running" ] default: "Stopped" - guestShutdownStops: - description: >- - If true, sets the state to "Stopped" when - the VM terminates due to a shutdown by the guest. - type: boolean - default: false machineUuid: description: >- The machine's uuid. If none is specified, a uuid diff --git a/dev-example/test-vm.yaml b/dev-example/test-vm.yaml index dcb3454..0a8a098 100644 --- a/dev-example/test-vm.yaml +++ b/dev-example/test-vm.yaml @@ -13,7 +13,9 @@ spec: requests: cpu: 1 memory: 2Gi - + + guestShutdownStops: true + cloudInit: {} vm: diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml index 64822a5..451a465 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml @@ -45,6 +45,9 @@ data: updateTemplate: ${ cr.spec.runnerTemplate.update.asBoolean?c } + # Whether a shutdown initiated by the guest stops the pod deployment + guestShutdownStops: ${ cr.spec.guestShutdownStops!false?c } + # Forward the cloud-init data if provided <#if cr.spec.cloudInit??> cloudInit: @@ -63,8 +66,6 @@ data: - guestShutdownStops: ${ cr.spec.vm.guestShutdownStops!false?c } - # Define the VM (required) vm: # The VM's name (required) From ee2de96c568ab7b2635f831e2a3513532c4475d4 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 26 Feb 2024 15:19:29 +0100 Subject: [PATCH 125/462] Fix javadoc. --- .../jdrupes/vmoperator/runner/qemu/events/ShutdownEvent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ShutdownEvent.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ShutdownEvent.java index e46bbd3..1804232 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ShutdownEvent.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ShutdownEvent.java @@ -21,7 +21,7 @@ package org.jdrupes.vmoperator.runner.qemu.events; import com.fasterxml.jackson.databind.JsonNode; /** - * Signals the processing of the {@link QmpShutdown} event. + * Signals the reception of a SHUTDOWN event. */ public class ShutdownEvent extends MonitorEvent { From a2641da7f5a7212aa6ea44c3128fa512ed94067c Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:12:37 +0100 Subject: [PATCH 126/462] Refactor internal Kubernetes API and upgrade to official v19 (#19) --- checkstyle.xml | 3 + dev-example/test-vm.yaml | 4 +- .../.settings/net.sf.jautodoc.prefs | 7 + org.jdrupes.vmoperator.common/build.gradle | 3 +- .../org/jdrupes/vmoperator/common/K8s.java | 234 +++--- .../jdrupes/vmoperator/common/K8sClient.java | 759 ++++++++++++++++++ .../vmoperator/common/K8sDynamicModel.java | 114 +++ .../K8sDynamicModelTypeAdapterFactory.java | 130 +++ .../vmoperator/common/K8sDynamicModels.java | 163 ++++ .../vmoperator/common/K8sDynamicStub.java | 109 +++ .../vmoperator/common/K8sGenericStub.java | 418 ++++++++++ .../vmoperator/common/K8sV1ConfigMapStub.java | 60 ++ .../common/K8sV1DeploymentStub.java | 77 ++ .../vmoperator/common/K8sV1PodStub.java | 78 ++ .../common/K8sV1StatefulSetStub.java | 60 ++ .../vmoperator/manager/events/VmChannel.java | 16 +- .../manager/events/VmDefChanged.java | 8 +- .../.settings/net.sf.jautodoc.prefs | 2 +- org.jdrupes.vmoperator.manager/build.gradle | 2 - .../vmoperator/manager/Controller.java | 38 +- .../manager/LoadBalancerReconciler.java | 38 +- .../vmoperator/manager/Reconciler.java | 5 +- .../manager/StatefulSetReconciler.java | 25 +- .../jdrupes/vmoperator/manager/VmWatcher.java | 74 +- .../vmoperator/manager/BasicTests.java | 97 +-- .../build.gradle | 2 +- .../vmoperator/runner/qemu/StatusUpdater.java | 157 ++-- .../jdrupes/vmoperator/vmconlet/VmConlet.java | 55 +- 28 files changed, 2343 insertions(+), 395 deletions(-) create mode 100644 org.jdrupes.vmoperator.common/.settings/net.sf.jautodoc.prefs create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sClient.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModel.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelTypeAdapterFactory.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModels.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStub.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sGenericStub.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ConfigMapStub.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1DeploymentStub.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1PodStub.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1StatefulSetStub.java diff --git a/checkstyle.xml b/checkstyle.xml index 015ef09..088e543 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -30,8 +30,11 @@ + + + diff --git a/dev-example/test-vm.yaml b/dev-example/test-vm.yaml index 0a8a098..19144d5 100644 --- a/dev-example/test-vm.yaml +++ b/dev-example/test-vm.yaml @@ -13,11 +13,11 @@ spec: requests: cpu: 1 memory: 2Gi - + guestShutdownStops: true cloudInit: {} - + vm: # state: Running bootMenu: yes diff --git a/org.jdrupes.vmoperator.common/.settings/net.sf.jautodoc.prefs b/org.jdrupes.vmoperator.common/.settings/net.sf.jautodoc.prefs new file mode 100644 index 0000000..8b8b906 --- /dev/null +++ b/org.jdrupes.vmoperator.common/.settings/net.sf.jautodoc.prefs @@ -0,0 +1,7 @@ +add_header=true +eclipse.preferences.version=1 +header_text=/*\n * VM-Operator\n * Copyright (C) 2024 Michael N. Lipp\n * \n * This program is free software\: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n */ +project_specific_settings=true +visibility_package=false +visibility_private=false +visibility_protected=false diff --git a/org.jdrupes.vmoperator.common/build.gradle b/org.jdrupes.vmoperator.common/build.gradle index ed082a1..42c05ae 100644 --- a/org.jdrupes.vmoperator.common/build.gradle +++ b/org.jdrupes.vmoperator.common/build.gradle @@ -10,5 +10,6 @@ plugins { dependencies { api project(':org.jdrupes.vmoperator.util') - api 'io.kubernetes:client-java:[18.0.0,19)' + api 'io.kubernetes:client-java:[19.0.0,20.0.0)' + api 'org.yaml:snakeyaml' } diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java index f61b431..e350cf1 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java @@ -1,6 +1,6 @@ /* * VM-Operator - * Copyright (C) 2023 Michael N. Lipp + * Copyright (C) 2023,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 @@ -18,29 +18,31 @@ package org.jdrupes.vmoperator.common; +import com.google.gson.JsonObject; +import io.kubernetes.client.Discovery; +import io.kubernetes.client.Discovery.APIResource; import io.kubernetes.client.common.KubernetesListObject; import io.kubernetes.client.common.KubernetesObject; +import io.kubernetes.client.common.KubernetesType; import io.kubernetes.client.custom.V1Patch; import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; -import io.kubernetes.client.openapi.apis.ApisApi; -import io.kubernetes.client.openapi.apis.CustomObjectsApi; -import io.kubernetes.client.openapi.models.V1APIGroup; -import io.kubernetes.client.openapi.models.V1ConfigMap; -import io.kubernetes.client.openapi.models.V1ConfigMapList; -import io.kubernetes.client.openapi.models.V1GroupVersionForDiscovery; +import io.kubernetes.client.openapi.apis.EventsV1Api; +import io.kubernetes.client.openapi.models.EventsV1Event; import io.kubernetes.client.openapi.models.V1ObjectMeta; import io.kubernetes.client.openapi.models.V1ObjectReference; -import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim; -import io.kubernetes.client.openapi.models.V1PersistentVolumeClaimList; -import io.kubernetes.client.openapi.models.V1Pod; -import io.kubernetes.client.openapi.models.V1PodList; +import io.kubernetes.client.util.Strings; import io.kubernetes.client.util.generic.GenericKubernetesApi; -import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; -import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; -import io.kubernetes.client.util.generic.options.DeleteOptions; +import io.kubernetes.client.util.generic.KubernetesApiResponse; import io.kubernetes.client.util.generic.options.PatchOptions; +import java.io.Reader; +import java.net.HttpURLConnection; +import java.time.OffsetDateTime; +import java.util.Map; import java.util.Optional; +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; /** * Helpers for K8s API. @@ -50,89 +52,80 @@ import java.util.Optional; public class K8s { /** - * Given a groupVersion, returns only the version. + * Returns the result from an API call as {@link Optional} if the + * call was successful. Returns an empty `Optional` if the status + * code is 404 (not found). Else throws an exception. * - * @param groupVersion the group version - * @return the string + * @param the generic type + * @param response the response + * @return the optional + * @throws ApiException the API exception */ - public static String version(String groupVersion) { - return groupVersion.substring(groupVersion.lastIndexOf('/') + 1); + public static Optional + optional(KubernetesApiResponse response) throws ApiException { + if (response.isSuccess()) { + return Optional.of(response.getObject()); + } + if (response.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { + return Optional.empty(); + } + response.throwsApiException(); + // Never reached + return Optional.empty(); } /** - * Get PVC API. + * Convert Yaml to Json. * * @param client the client - * @return the generic kubernetes api + * @param yaml the yaml + * @return the json element */ - public static GenericKubernetesApi pvcApi(ApiClient client) { - return new GenericKubernetesApi<>(V1PersistentVolumeClaim.class, - V1PersistentVolumeClaimList.class, "", "v1", - "persistentvolumeclaims", client); + public static JsonObject yamlToJson(ApiClient client, Reader yaml) { + // Avoid Yaml.load due to + // https://github.com/kubernetes-client/java/issues/2741 + @SuppressWarnings("PMD.UseConcurrentHashMap") + Map yamlData + = new Yaml(new SafeConstructor(new LoaderOptions())).load(yaml); + + // There's no short-cut from Java (collections) to Gson + var gson = client.getJSON().getGson(); + var jsonText = gson.toJson(yamlData); + return gson.fromJson(jsonText, JsonObject.class); } /** - * Get config map API. - * - * @param client the client - * @return the generic kubernetes api - */ - public static GenericKubernetesApi cmApi(ApiClient client) { - return new GenericKubernetesApi<>(V1ConfigMap.class, - V1ConfigMapList.class, "", "v1", "configmaps", client); - } - - /** - * Get pod API. - * - * @param client the client - * @return the generic kubernetes api - */ - public static GenericKubernetesApi - podApi(ApiClient client) { - return new GenericKubernetesApi<>(V1Pod.class, V1PodList.class, "", - "v1", "pods", client); - } - - /** - * Get the API for a custom resource. + * Lookup the specified API resource. If the version is `null` or + * empty, the preferred version in the result is the default + * returned from the server. * * @param client the client * @param group the group + * @param version the version * @param kind the kind - * @param namespace the namespace - * @param name the name - * @return the dynamic kubernetes api + * @return the optional * @throws ApiException the api exception */ - @SuppressWarnings("PMD.UseObjectForClearerAPI") - public static Optional crApi(ApiClient client, - String group, String kind, String namespace, String name) - throws ApiException { - var apis = new ApisApi(client).getAPIVersions(); - var crdVersions = apis.getGroups().stream() - .filter(g -> g.getName().equals(group)).findFirst() - .map(V1APIGroup::getVersions).stream().flatMap(l -> l.stream()) - .map(V1GroupVersionForDiscovery::getVersion).toList(); - var coa = new CustomObjectsApi(client); - for (var crdVersion : crdVersions) { - var crdApiRes = coa.getAPIResources(group, crdVersion) - .getResources().stream().filter(r -> kind.equals(r.getKind())) - .findFirst(); - if (crdApiRes.isEmpty()) { - continue; - } - @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") - var crApi = new DynamicKubernetesApi(group, - crdVersion, crdApiRes.get().getName(), client); - var customResource = crApi.get(namespace, name); - if (customResource.isSuccess()) { - return Optional.of(crApi); - } + public static Optional context(ApiClient client, + String group, String version, String kind) throws ApiException { + var apiMatch = new Discovery(client).findAll().stream() + .filter(r -> r.getGroup().equals(group) && r.getKind().equals(kind) + && (Strings.isNullOrEmpty(version) + || r.getVersions().contains(version))) + .findFirst(); + if (apiMatch.isEmpty()) { + return Optional.empty(); } - return Optional.empty(); + var apiRes = apiMatch.get(); + if (!Strings.isNullOrEmpty(version)) { + if (!apiRes.getVersions().contains(version)) { + return Optional.empty(); + } + apiRes = new APIResource(apiRes.getGroup(), apiRes.getVersions(), + version, apiRes.getKind(), apiRes.getNamespaced(), + apiRes.getResourcePlural(), apiRes.getResourceSingular()); + } + return Optional.of(apiRes); } /** @@ -144,6 +137,7 @@ public class K8s { * @param meta the meta * @return the object */ + @Deprecated public static Optional get(GenericKubernetesApi api, V1ObjectMeta meta) { @@ -154,36 +148,6 @@ public class K8s { return Optional.empty(); } - /** - * Delete an object. - * - * @param the generic type - * @param the generic type - * @param api the api - * @param object the object - */ - public static - void delete(GenericKubernetesApi api, T object) - throws ApiException { - api.delete(object.getMetadata().getNamespace(), - object.getMetadata().getName()).throwsApiException(); - } - - /** - * Delete an object. - * - * @param the generic type - * @param the generic type - * @param api the api - * @param object the object - */ - public static - void delete(GenericKubernetesApi api, T object, - DeleteOptions options) throws ApiException { - api.delete(object.getMetadata().getNamespace(), - object.getMetadata().getName(), options).throwsApiException(); - } - /** * Apply the given patch data. * @@ -213,7 +177,7 @@ public class K8s { * @return the v 1 object reference */ public static V1ObjectReference - objectReference(DynamicKubernetesObject object) { + objectReference(KubernetesObject object) { return new V1ObjectReference().apiVersion(object.getApiVersion()) .kind(object.getKind()) .namespace(object.getMetadata().getNamespace()) @@ -221,4 +185,54 @@ public class K8s { .resourceVersion(object.getMetadata().getResourceVersion()) .uid(object.getMetadata().getUid()); } + + /** + * Creates an event related to the object, adding reasonable defaults. + * + * * If `kind` is not set, it is set to "Event". + * * If `metadata.namespace` is not set, it is set + * to the object's namespace. + * * If neither `metadata.name` nor `matadata.generateName` are set, + * set `generateName` to the object's name with a dash appended. + * * If `reportingInstance` is not set, set it to the object's name. + * * If `eventTime` is not set, set it to now. + * * If `type` is not set, set it to "Normal" + * * If `regarding` is not set, set it to the given object. + * + * @param event the event + * @throws ApiException + */ + @SuppressWarnings("PMD.NPathComplexity") + public static void createEvent(ApiClient client, + KubernetesObject object, EventsV1Event event) + throws ApiException { + if (Strings.isNullOrEmpty(event.getKind())) { + event.kind("Event"); + } + if (event.getMetadata() == null) { + event.metadata(new V1ObjectMeta()); + } + if (Strings.isNullOrEmpty(event.getMetadata().getNamespace())) { + event.getMetadata().namespace(object.getMetadata().getNamespace()); + } + if (Strings.isNullOrEmpty(event.getMetadata().getName()) + && Strings.isNullOrEmpty(event.getMetadata().getGenerateName())) { + event.getMetadata() + .generateName(object.getMetadata().getName() + "-"); + } + if (Strings.isNullOrEmpty(event.getReportingInstance())) { + event.reportingInstance(object.getMetadata().getName()); + } + if (event.getEventTime() == null) { + event.eventTime(OffsetDateTime.now()); + } + if (Strings.isNullOrEmpty(event.getType())) { + event.type("Normal"); + } + if (event.getRegarding() == null) { + event.regarding(objectReference(object)); + } + new EventsV1Api(client).createNamespacedEvent( + object.getMetadata().getNamespace(), event, null, null, null, null); + } } diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sClient.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sClient.java new file mode 100644 index 0000000..b7106fb --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sClient.java @@ -0,0 +1,759 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import io.kubernetes.client.openapi.ApiCallback; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.ApiResponse; +import io.kubernetes.client.openapi.JSON; +import io.kubernetes.client.openapi.Pair; +import io.kubernetes.client.openapi.auth.Authentication; +import io.kubernetes.client.util.ClientBuilder; +import io.kubernetes.client.util.generic.options.PatchOptions; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.text.DateFormat; +import java.time.format.DateTimeFormatter; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import javax.net.ssl.KeyManager; +import okhttp3.Call; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Request.Builder; +import okhttp3.RequestBody; +import okhttp3.Response; + +/** + * A client with some additional properties. + */ +@SuppressWarnings({ "PMD.ExcessivePublicCount", "PMD.TooManyMethods", + "PMD.LinguisticNaming", "checkstyle:LineLength" }) +public class K8sClient extends ApiClient { + + private ApiClient apiClient; + private PatchOptions defaultPatchOptions; + + /** + * Instantiates a new client. + * + * @throws IOException Signals that an I/O exception has occurred. + */ + public K8sClient() throws IOException { + defaultPatchOptions = new PatchOptions(); + defaultPatchOptions.setFieldManager("kubernetes-java-kubectl-apply"); + } + + private ApiClient apiClient() { + if (apiClient == null) { + try { + apiClient = ClientBuilder.standard().build(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + return apiClient; + } + + /** + * Gets the default patch options. + * + * @return the defaultPatchOptions + */ + public PatchOptions defaultPatchOptions() { + return defaultPatchOptions; + } + + /** + * Changes the default patch options. + * + * @param patchOptions the patch options + * @return the client + */ + public K8sClient with(PatchOptions patchOptions) { + defaultPatchOptions = patchOptions; + return this; + } + + /** + * @return + * @see ApiClient#getBasePath() + */ + public String getBasePath() { + return apiClient().getBasePath(); + } + + /** + * @param basePath + * @return + * @see ApiClient#setBasePath(java.lang.String) + */ + public ApiClient setBasePath(String basePath) { + return apiClient().setBasePath(basePath); + } + + /** + * @return + * @see ApiClient#getHttpClient() + */ + public OkHttpClient getHttpClient() { + return apiClient().getHttpClient(); + } + + /** + * @param newHttpClient + * @return + * @see ApiClient#setHttpClient(okhttp3.OkHttpClient) + */ + public ApiClient setHttpClient(OkHttpClient newHttpClient) { + return apiClient().setHttpClient(newHttpClient); + } + + /** + * @return + * @see ApiClient#getJSON() + */ + @SuppressWarnings("abbreviationAsWordInName") + public JSON getJSON() { + return apiClient().getJSON(); + } + + /** + * @param json + * @return + * @see ApiClient#setJSON(io.kubernetes.client.openapi.JSON) + */ + @SuppressWarnings("abbreviationAsWordInName") + public ApiClient setJSON(JSON json) { + return apiClient().setJSON(json); + } + + /** + * @return + * @see ApiClient#isVerifyingSsl() + */ + public boolean isVerifyingSsl() { + return apiClient().isVerifyingSsl(); + } + + /** + * @param verifyingSsl + * @return + * @see ApiClient#setVerifyingSsl(boolean) + */ + public ApiClient setVerifyingSsl(boolean verifyingSsl) { + return apiClient().setVerifyingSsl(verifyingSsl); + } + + /** + * @return + * @see ApiClient#getSslCaCert() + */ + public InputStream getSslCaCert() { + return apiClient().getSslCaCert(); + } + + /** + * @param sslCaCert + * @return + * @see ApiClient#setSslCaCert(java.io.InputStream) + */ + public ApiClient setSslCaCert(InputStream sslCaCert) { + return apiClient().setSslCaCert(sslCaCert); + } + + /** + * @return + * @see ApiClient#getKeyManagers() + */ + public KeyManager[] getKeyManagers() { + return apiClient().getKeyManagers(); + } + + /** + * @param managers + * @return + * @see ApiClient#setKeyManagers(javax.net.ssl.KeyManager[]) + */ + @SuppressWarnings("PMD.UseVarargs") + public ApiClient setKeyManagers(KeyManager[] managers) { + return apiClient().setKeyManagers(managers); + } + + /** + * @return + * @see ApiClient#getDateFormat() + */ + public DateFormat getDateFormat() { + return apiClient().getDateFormat(); + } + + /** + * @param dateFormat + * @return + * @see ApiClient#setDateFormat(java.text.DateFormat) + */ + public ApiClient setDateFormat(DateFormat dateFormat) { + return apiClient().setDateFormat(dateFormat); + } + + /** + * @param dateFormat + * @return + * @see ApiClient#setSqlDateFormat(java.text.DateFormat) + */ + public ApiClient setSqlDateFormat(DateFormat dateFormat) { + return apiClient().setSqlDateFormat(dateFormat); + } + + /** + * @param dateFormat + * @return + * @see ApiClient#setOffsetDateTimeFormat(java.time.format.DateTimeFormatter) + */ + public ApiClient setOffsetDateTimeFormat(DateTimeFormatter dateFormat) { + return apiClient().setOffsetDateTimeFormat(dateFormat); + } + + /** + * @param dateFormat + * @return + * @see ApiClient#setLocalDateFormat(java.time.format.DateTimeFormatter) + */ + public ApiClient setLocalDateFormat(DateTimeFormatter dateFormat) { + return apiClient().setLocalDateFormat(dateFormat); + } + + /** + * @param lenientOnJson + * @return + * @see ApiClient#setLenientOnJson(boolean) + */ + public ApiClient setLenientOnJson(boolean lenientOnJson) { + return apiClient().setLenientOnJson(lenientOnJson); + } + + /** + * @return + * @see ApiClient#getAuthentications() + */ + public Map getAuthentications() { + return apiClient().getAuthentications(); + } + + /** + * @param authName + * @return + * @see ApiClient#getAuthentication(java.lang.String) + */ + public Authentication getAuthentication(String authName) { + return apiClient().getAuthentication(authName); + } + + /** + * @param username + * @see ApiClient#setUsername(java.lang.String) + */ + public void setUsername(String username) { + apiClient().setUsername(username); + } + + /** + * @param password + * @see ApiClient#setPassword(java.lang.String) + */ + public void setPassword(String password) { + apiClient().setPassword(password); + } + + /** + * @param apiKey + * @see ApiClient#setApiKey(java.lang.String) + */ + public void setApiKey(String apiKey) { + apiClient().setApiKey(apiKey); + } + + /** + * @param apiKeyPrefix + * @see ApiClient#setApiKeyPrefix(java.lang.String) + */ + public void setApiKeyPrefix(String apiKeyPrefix) { + apiClient().setApiKeyPrefix(apiKeyPrefix); + } + + /** + * @param accessToken + * @see ApiClient#setAccessToken(java.lang.String) + */ + public void setAccessToken(String accessToken) { + apiClient().setAccessToken(accessToken); + } + + /** + * @param userAgent + * @return + * @see ApiClient#setUserAgent(java.lang.String) + */ + public ApiClient setUserAgent(String userAgent) { + return apiClient().setUserAgent(userAgent); + } + + /** + * @return + * @see java.lang.Object#toString() + */ + public String toString() { + return apiClient().toString(); + } + + /** + * @param key + * @param value + * @return + * @see ApiClient#addDefaultHeader(java.lang.String, java.lang.String) + */ + public ApiClient addDefaultHeader(String key, String value) { + return apiClient().addDefaultHeader(key, value); + } + + /** + * @param key + * @param value + * @return + * @see ApiClient#addDefaultCookie(java.lang.String, java.lang.String) + */ + public ApiClient addDefaultCookie(String key, String value) { + return apiClient().addDefaultCookie(key, value); + } + + /** + * @return + * @see ApiClient#isDebugging() + */ + public boolean isDebugging() { + return apiClient().isDebugging(); + } + + /** + * @param debugging + * @return + * @see ApiClient#setDebugging(boolean) + */ + public ApiClient setDebugging(boolean debugging) { + return apiClient().setDebugging(debugging); + } + + /** + * @return + * @see ApiClient#getTempFolderPath() + */ + public String getTempFolderPath() { + return apiClient().getTempFolderPath(); + } + + /** + * @param tempFolderPath + * @return + * @see ApiClient#setTempFolderPath(java.lang.String) + */ + public ApiClient setTempFolderPath(String tempFolderPath) { + return apiClient().setTempFolderPath(tempFolderPath); + } + + /** + * @return + * @see ApiClient#getConnectTimeout() + */ + public int getConnectTimeout() { + return apiClient().getConnectTimeout(); + } + + /** + * @param connectionTimeout + * @return + * @see ApiClient#setConnectTimeout(int) + */ + public ApiClient setConnectTimeout(int connectionTimeout) { + return apiClient().setConnectTimeout(connectionTimeout); + } + + /** + * @return + * @see ApiClient#getReadTimeout() + */ + public int getReadTimeout() { + return apiClient().getReadTimeout(); + } + + /** + * @param readTimeout + * @return + * @see ApiClient#setReadTimeout(int) + */ + public ApiClient setReadTimeout(int readTimeout) { + return apiClient().setReadTimeout(readTimeout); + } + + /** + * @return + * @see ApiClient#getWriteTimeout() + */ + public int getWriteTimeout() { + return apiClient().getWriteTimeout(); + } + + /** + * @param writeTimeout + * @return + * @see ApiClient#setWriteTimeout(int) + */ + public ApiClient setWriteTimeout(int writeTimeout) { + return apiClient().setWriteTimeout(writeTimeout); + } + + /** + * @param param + * @return + * @see ApiClient#parameterToString(java.lang.Object) + */ + public String parameterToString(Object param) { + return apiClient().parameterToString(param); + } + + /** + * @param name + * @param value + * @return + * @see ApiClient#parameterToPair(java.lang.String, java.lang.Object) + */ + public List parameterToPair(String name, Object value) { + return apiClient().parameterToPair(name, value); + } + + /** + * @param collectionFormat + * @param name + * @param value + * @return + * @see ApiClient#parameterToPairs(java.lang.String, java.lang.String, java.util.Collection) + */ + @SuppressWarnings({ "rawtypes", "PMD.AvoidDuplicateLiterals" }) + public List parameterToPairs(String collectionFormat, String name, + Collection value) { + return apiClient().parameterToPairs(collectionFormat, name, value); + } + + /** + * @param collectionFormat + * @param value + * @return + * @see ApiClient#collectionPathParameterToString(java.lang.String, java.util.Collection) + */ + @SuppressWarnings("rawtypes") + public String collectionPathParameterToString(String collectionFormat, + Collection value) { + return apiClient().collectionPathParameterToString(collectionFormat, + value); + } + + /** + * @param filename + * @return + * @see ApiClient#sanitizeFilename(java.lang.String) + */ + public String sanitizeFilename(String filename) { + return apiClient().sanitizeFilename(filename); + } + + /** + * @param mime + * @return + * @see ApiClient#isJsonMime(java.lang.String) + */ + public boolean isJsonMime(String mime) { + return apiClient().isJsonMime(mime); + } + + /** + * @param accepts + * @return + * @see ApiClient#selectHeaderAccept(java.lang.String[]) + */ + @SuppressWarnings("PMD.UseVarargs") + public String selectHeaderAccept(String[] accepts) { + return apiClient().selectHeaderAccept(accepts); + } + + /** + * @param contentTypes + * @return + * @see ApiClient#selectHeaderContentType(java.lang.String[]) + */ + @SuppressWarnings("PMD.UseVarargs") + public String selectHeaderContentType(String[] contentTypes) { + return apiClient().selectHeaderContentType(contentTypes); + } + + /** + * @param str + * @return + * @see ApiClient#escapeString(java.lang.String) + */ + public String escapeString(String str) { + return apiClient().escapeString(str); + } + + /** + * @param + * @param response + * @param returnType + * @return + * @throws ApiException + * @see ApiClient#deserialize(okhttp3.Response, java.lang.reflect.Type) + */ + public T deserialize(Response response, Type returnType) + throws ApiException { + return apiClient().deserialize(response, returnType); + } + + /** + * @param obj + * @param contentType + * @return + * @throws ApiException + * @see ApiClient#serialize(java.lang.Object, java.lang.String) + */ + public RequestBody serialize(Object obj, String contentType) + throws ApiException { + return apiClient().serialize(obj, contentType); + } + + /** + * @param response + * @return + * @throws ApiException + * @see ApiClient#downloadFileFromResponse(okhttp3.Response) + */ + public File downloadFileFromResponse(Response response) + throws ApiException { + return apiClient().downloadFileFromResponse(response); + } + + /** + * @param response + * @return + * @throws IOException + * @see ApiClient#prepareDownloadFile(okhttp3.Response) + */ + public File prepareDownloadFile(Response response) throws IOException { + return apiClient().prepareDownloadFile(response); + } + + /** + * @param + * @param call + * @return + * @throws ApiException + * @see ApiClient#execute(okhttp3.Call) + */ + public ApiResponse execute(Call call) throws ApiException { + return apiClient().execute(call); + } + + /** + * @param + * @param call + * @param returnType + * @return + * @throws ApiException + * @see ApiClient#execute(okhttp3.Call, java.lang.reflect.Type) + */ + public ApiResponse execute(Call call, Type returnType) + throws ApiException { + return apiClient().execute(call, returnType); + } + + /** + * @param + * @param call + * @param callback + * @see ApiClient#executeAsync(okhttp3.Call, io.kubernetes.client.openapi.ApiCallback) + */ + public void executeAsync(Call call, ApiCallback callback) { + apiClient().executeAsync(call, callback); + } + + /** + * @param + * @param call + * @param returnType + * @param callback + * @see ApiClient#executeAsync(okhttp3.Call, java.lang.reflect.Type, io.kubernetes.client.openapi.ApiCallback) + */ + public void executeAsync(Call call, Type returnType, + ApiCallback callback) { + apiClient().executeAsync(call, returnType, callback); + } + + /** + * @param + * @param response + * @param returnType + * @return + * @throws ApiException + * @see ApiClient#handleResponse(okhttp3.Response, java.lang.reflect.Type) + */ + public T handleResponse(Response response, Type returnType) + throws ApiException { + return apiClient().handleResponse(response, returnType); + } + + /** + * @param path + * @param method + * @param queryParams + * @param collectionQueryParams + * @param body + * @param headerParams + * @param cookieParams + * @param formParams + * @param authNames + * @param callback + * @return + * @throws ApiException + * @see ApiClient#buildCall(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.Object, java.util.Map, java.util.Map, java.util.Map, java.lang.String[], io.kubernetes.client.openapi.ApiCallback) + */ + @SuppressWarnings({ "rawtypes", "PMD.ExcessiveParameterList" }) + public Call buildCall(String path, String method, List queryParams, + List collectionQueryParams, Object body, + Map headerParams, Map cookieParams, + Map formParams, String[] authNames, + ApiCallback callback) throws ApiException { + return apiClient().buildCall(path, method, queryParams, + collectionQueryParams, body, headerParams, cookieParams, formParams, + authNames, callback); + } + + /** + * @param path + * @param method + * @param queryParams + * @param collectionQueryParams + * @param body + * @param headerParams + * @param cookieParams + * @param formParams + * @param authNames + * @param callback + * @return + * @throws ApiException + * @see ApiClient#buildRequest(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.Object, java.util.Map, java.util.Map, java.util.Map, java.lang.String[], io.kubernetes.client.openapi.ApiCallback) + */ + @SuppressWarnings({ "rawtypes", "PMD.ExcessiveParameterList" }) + public Request buildRequest(String path, String method, + List queryParams, List collectionQueryParams, + Object body, Map headerParams, + Map cookieParams, Map formParams, + String[] authNames, ApiCallback callback) throws ApiException { + return apiClient().buildRequest(path, method, queryParams, + collectionQueryParams, body, headerParams, cookieParams, formParams, + authNames, callback); + } + + /** + * @param path + * @param queryParams + * @param collectionQueryParams + * @return + * @see ApiClient#buildUrl(java.lang.String, java.util.List, java.util.List) + */ + public String buildUrl(String path, List queryParams, + List collectionQueryParams) { + return apiClient().buildUrl(path, queryParams, collectionQueryParams); + } + + /** + * @param headerParams + * @param reqBuilder + * @see ApiClient#processHeaderParams(java.util.Map, okhttp3.Request.Builder) + */ + public void processHeaderParams(Map headerParams, + Builder reqBuilder) { + apiClient().processHeaderParams(headerParams, reqBuilder); + } + + /** + * @param cookieParams + * @param reqBuilder + * @see ApiClient#processCookieParams(java.util.Map, okhttp3.Request.Builder) + */ + public void processCookieParams(Map cookieParams, + Builder reqBuilder) { + apiClient().processCookieParams(cookieParams, reqBuilder); + } + + /** + * @param authNames + * @param queryParams + * @param headerParams + * @param cookieParams + * @see ApiClient#updateParamsForAuth(java.lang.String[], java.util.List, java.util.Map, java.util.Map) + */ + public void updateParamsForAuth(String[] authNames, List queryParams, + Map headerParams, + Map cookieParams) { + apiClient().updateParamsForAuth(authNames, queryParams, headerParams, + cookieParams); + } + + /** + * @param formParams + * @return + * @see ApiClient#buildRequestBodyFormEncoding(java.util.Map) + */ + public RequestBody + buildRequestBodyFormEncoding(Map formParams) { + return apiClient().buildRequestBodyFormEncoding(formParams); + } + + /** + * @param formParams + * @return + * @see ApiClient#buildRequestBodyMultipart(java.util.Map) + */ + public RequestBody + buildRequestBodyMultipart(Map formParams) { + return apiClient().buildRequestBodyMultipart(formParams); + } + + /** + * @param file + * @return + * @see ApiClient#guessContentTypeFromFile(java.io.File) + */ + public String guessContentTypeFromFile(File file) { + return apiClient().guessContentTypeFromFile(file); + } + +} \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModel.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModel.java new file mode 100644 index 0000000..6a4410f --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModel.java @@ -0,0 +1,114 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import io.kubernetes.client.common.KubernetesObject; +import io.kubernetes.client.openapi.models.V1ObjectMeta; + +/** + * Represents a Kubernetes object using a JSON data structure. + * Some information that is common to all Kubernetes objects, + * notably the metadata, is made available through the methods + * defined by {@link KubernetesObject}. + */ +@SuppressWarnings("PMD.DataClass") +public class K8sDynamicModel implements KubernetesObject { + + private final V1ObjectMeta metadata; + private final JsonObject data; + + /** + * Instantiates a new model from the JSON representation. + * + * @param delegate the gson instance to use for extracting structured data + * @param json the JSON + */ + public K8sDynamicModel(Gson delegate, JsonObject json) { + this.data = json; + metadata = delegate.fromJson(data.get("metadata"), V1ObjectMeta.class); + } + + @Override + public String getApiVersion() { + return apiVersion(); + } + + /** + * Gets the API version. (Abbreviated method name for convenience.) + * + * @return the API version + */ + public String apiVersion() { + return data.get("apiVersion").getAsString(); + } + + @Override + public String getKind() { + return kind(); + } + + /** + * Gets the kind. (Abbreviated method name for convenience.) + * + * @return the kind + */ + public String kind() { + return data.get("kind").getAsString(); + } + + @Override + public V1ObjectMeta getMetadata() { + return metadata; + } + + /** + * Gets the metadata. (Abbreviated method name for convenience.) + * + * @return the metadata + */ + public V1ObjectMeta metadata() { + return metadata; + } + + /** + * Gets the data. + * + * @return the data + */ + public JsonObject data() { + return data; + } + + /** + * Convenience method for getting the status. + * + * @return the JSON object describing the status + */ + public JsonObject status() { + return data.getAsJsonObject("status"); + } + + @Override + public String toString() { + return data.toString(); + } + +} diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelTypeAdapterFactory.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelTypeAdapterFactory.java new file mode 100644 index 0000000..9018744 --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelTypeAdapterFactory.java @@ -0,0 +1,130 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import com.google.gson.Gson; +import com.google.gson.InstanceCreator; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.lang.reflect.Type; + +/** + * A factory for creating K8sDynamicModel(s) objects. + */ +public class K8sDynamicModelTypeAdapterFactory implements TypeAdapterFactory { + + /** + * Creates a type adapter for the given type. + * + * @param the generic type + * @param gson the gson + * @param typeToken the type token + * @return the type adapter or null if the type is not handles by + * this factory + */ + @SuppressWarnings("unchecked") + public TypeAdapter create(Gson gson, TypeToken typeToken) { + if (TypeToken.get(K8sDynamicModel.class).equals(typeToken)) { + return (TypeAdapter) (new K8sDynamicModelCreator(gson)); + } + if (TypeToken.get(K8sDynamicModels.class).equals(typeToken)) { + return (TypeAdapter) (new K8sDynamicModelsCreator(gson)); + } + return null; + } + + /** + * The Class K8sDynamicModelCreator. + */ + /* default */ class K8sDynamicModelCreator + extends TypeAdapter + implements InstanceCreator { + private final Gson delegate; + + /** + * Instantiates a new object state creator. + * + * @param delegate the delegate + */ + public K8sDynamicModelCreator(Gson delegate) { + this.delegate = delegate; + } + + @Override + public K8sDynamicModel createInstance(Type type) { + return new K8sDynamicModel(delegate, null); + } + + @Override + public void write(JsonWriter jsonWriter, K8sDynamicModel state) + throws IOException { + jsonWriter.jsonValue(delegate.toJson(state.data())); + } + + @Override + public K8sDynamicModel read(JsonReader jsonReader) + throws IOException { + return new K8sDynamicModel(delegate, + delegate.fromJson(jsonReader, JsonObject.class)); + } + } + + /** + * The Class K8sDynamicModelsCreator. + */ + /* default */class K8sDynamicModelsCreator + extends TypeAdapter + implements InstanceCreator { + + private final Gson delegate; + + /** + * Instantiates a new object states creator. + * + * @param delegate the delegate + */ + public K8sDynamicModelsCreator(Gson delegate) { + this.delegate = delegate; + } + + @Override + public K8sDynamicModels createInstance(Type type) { + return new K8sDynamicModels(delegate, null); + } + + @Override + public void write(JsonWriter jsonWriter, K8sDynamicModels states) + throws IOException { + jsonWriter.jsonValue(delegate.toJson(states.data())); + } + + @Override + public K8sDynamicModels read(JsonReader jsonReader) + throws IOException { + return new K8sDynamicModels(delegate, + delegate.fromJson(jsonReader, JsonObject.class)); + } + } + +} diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModels.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModels.java new file mode 100644 index 0000000..165b10e --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModels.java @@ -0,0 +1,163 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.kubernetes.client.common.KubernetesListObject; +import io.kubernetes.client.openapi.Configuration; +import io.kubernetes.client.openapi.models.V1ListMeta; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Represents a list of Kubernetes objects each of which is + * represented using a JSON data structure. + * Some information that is common to all Kubernetes objects, + * notably the metadata, is made available through the methods + * defined by {@link KubernetesListObject}. + */ +public class K8sDynamicModels implements KubernetesListObject { + + private final JsonObject data; + private final V1ListMeta metadata; + private final List items; + + /** + * Initialize the object list using the given JSON data. + * + * @param delegate the gson instance to use for extracting structured data + * @param data the data + */ + public K8sDynamicModels(Gson delegate, JsonObject data) { + this.data = data; + metadata = delegate.fromJson(data.get("metadata"), V1ListMeta.class); + items = new ArrayList<>(); + for (JsonElement e : data.get("items").getAsJsonArray()) { + items.add(new K8sDynamicModel(delegate, e.getAsJsonObject())); + } + } + + @Override + public String getApiVersion() { + return apiVersion(); + } + + /** + * Gets the API version. (Abbreviated method name for convenience.) + * + * @return the API version + */ + public String apiVersion() { + return data.get("apiVersion").getAsString(); + } + + @Override + public String getKind() { + return kind(); + } + + /** + * Gets the kind. (Abbreviated method name for convenience.) + * + * @return the kind + */ + public String kind() { + return data.get("kind").getAsString(); + } + + @Override + public V1ListMeta getMetadata() { + return metadata; + } + + /** + * Gets the metadata. (Abbreviated method name for convenience.) + * + * @return the metadata + */ + public V1ListMeta metadata() { + return metadata; + } + + /** + * Returns the JSON representation of this object. + * + * @return the JOSN representation + */ + public JsonObject data() { + return data; + } + + @Override + public List getItems() { + return items; + } + + /** + * Sets the api version. + * + * @param apiVersion the new api version + */ + public void setApiVersion(String apiVersion) { + data.addProperty("apiVersion", apiVersion); + } + + /** + * Sets the kind. + * + * @param kind the new kind + */ + public void setKind(String kind) { + data.addProperty("kind", kind); + } + + /** + * Sets the metadata. + * + * @param objectMeta the new metadata + */ + public void setMetadata(V1ListMeta objectMeta) { + data.add("metadata", + Configuration.getDefaultApiClient().getJSON().getGson() + .toJsonTree(objectMeta)); + } + + @Override + public int hashCode() { + return Objects.hash(data); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + K8sDynamicModels other = (K8sDynamicModels) obj; + return Objects.equals(data, other.data); + } +} diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStub.java new file mode 100644 index 0000000..1ab33ca --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStub.java @@ -0,0 +1,109 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import io.kubernetes.client.Discovery.APIResource; +import io.kubernetes.client.apimachinery.GroupVersionKind; +import io.kubernetes.client.openapi.ApiException; +import java.io.Reader; + +/** + * A stub for namespaced custom objects. It uses a dynamic model + * (see {@link K8sDynamicModel}) for representing the object's + * state and can therefore be used for any kind of object, especially + * custom objects. + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class K8sDynamicStub + extends K8sGenericStub { + + /** + * Instantiates a new dynamic stub. + * + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the context + * @param namespace the namespace + * @param name the name + */ + public K8sDynamicStub(Class objectClass, + Class objectListClass, K8sClient client, + APIResource context, String namespace, String name) { + super(objectClass, objectListClass, client, context, namespace, name); + } + + /** + * Get a dynamic object stub. If the version in parameter + * `gvk` is an empty string, the stub refers to the first object with + * matching group and kind. + * + * @param client the client + * @param gvk the group, version and kind + * @param namespace the namespace + * @param name the name + * @return the stub if the object exists + * @throws ApiException the api exception + */ + @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", + "PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" }) + public static K8sDynamicStub get(K8sClient client, + GroupVersionKind gvk, String namespace, String name) + throws ApiException { + return K8sGenericStub.get(K8sDynamicModel.class, K8sDynamicModels.class, + client, gvk, namespace, name, K8sDynamicStub::new); + } + + /** + * Get a dynamic object stub. + * + * @param client the client + * @param context the context + * @param namespace the namespace + * @param name the name + * @return the stub if the object exists + * @throws ApiException the api exception + */ + @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", + "PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" }) + public static K8sDynamicStub get(K8sClient client, + APIResource context, String namespace, String name) + throws ApiException { + return K8sGenericStub.get(K8sDynamicModel.class, K8sDynamicModels.class, + client, context, namespace, name, K8sDynamicStub::new); + } + + /** + * Creates a stub from yaml. + * + * @param client the client + * @param context the context + * @param yaml the yaml + * @return the k 8 s dynamic stub + * @throws ApiException the api exception + */ + public static K8sDynamicStub createFromYaml(K8sClient client, + APIResource context, Reader yaml) throws ApiException { + var model = new K8sDynamicModel(client.getJSON().getGson(), + K8s.yamlToJson(client, yaml)); + return K8sGenericStub.create(K8sDynamicModel.class, + K8sDynamicModels.class, client, context, model, + K8sDynamicStub::new); + } +} \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sGenericStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sGenericStub.java new file mode 100644 index 0000000..30c6699 --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sGenericStub.java @@ -0,0 +1,418 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import com.google.gson.Gson; +import io.kubernetes.client.Discovery.APIResource; +import io.kubernetes.client.apimachinery.GroupVersionKind; +import io.kubernetes.client.common.KubernetesListObject; +import io.kubernetes.client.common.KubernetesObject; +import io.kubernetes.client.custom.V1Patch; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.util.Strings; +import io.kubernetes.client.util.generic.GenericKubernetesApi; +import io.kubernetes.client.util.generic.options.ListOptions; +import io.kubernetes.client.util.generic.options.PatchOptions; +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Optional; +import java.util.function.Function; + +/** + * A stub for namespaced custom objects. This stub provides the + * functions common to all Kubernetes objects, but uses variables + * for all types. This class should be used as base class only. + * + * @param the generic type + * @param the generic type + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class K8sGenericStub { + protected final K8sClient client; + private final GenericKubernetesApi api; + protected final String group; + protected final String version; + protected final String kind; + protected final String plural; + protected final String namespace; + protected final String name; + + /** + * Get a namespaced object stub. If the version in parameter + * `gvk` is an empty string, the stub refers to the first object + * found with matching group and kind. + * + * @param the object type + * @param the object list type + * @param the stub type + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param gvk the group, version and kind + * @param namespace the namespace + * @param name the name + * @param provider the provider + * @return the stub if the object exists + * @throws ApiException the api exception + */ + @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", + "PMD.AvoidInstantiatingObjectsInLoops" }) + public static > + R get(Class objectClass, Class objectListClass, + K8sClient client, GroupVersionKind gvk, String namespace, + String name, GenericSupplier provider) + throws ApiException { + var context = K8s.context(client, gvk.getGroup(), gvk.getVersion(), + gvk.getKind()); + if (context.isEmpty()) { + throw new ApiException("No known API for " + gvk.getGroup() + + "/" + gvk.getVersion() + " " + gvk.getKind()); + } + return provider.get(objectClass, objectListClass, client, context.get(), + namespace, name); + } + + /** + * Get a namespaced object stub. + * + * @param the object type + * @param the object list type + * @param the stub type + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the context + * @param namespace the namespace + * @param name the name + * @param provider the provider + * @return the stub if the object exists + * @throws ApiException the api exception + */ + @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", + "PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" }) + public static > + R get(Class objectClass, Class objectListClass, + K8sClient client, APIResource context, String namespace, + String name, GenericSupplier provider) + throws ApiException { + return provider.get(objectClass, objectListClass, client, + context, namespace, name); + } + + /** + * Get a namespaced object stub for a newly created object. + * + * @param the object type + * @param the object list type + * @param the stub type + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the context + * @param model the model + * @param provider the provider + * @return the stub if the object exists + * @throws ApiException the api exception + */ + @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", + "PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" }) + public static > + R create(Class objectClass, Class objectListClass, + K8sClient client, APIResource context, O model, + GenericSupplier provider) throws ApiException { + var api = new GenericKubernetesApi<>(objectClass, objectListClass, + context.getGroup(), context.getPreferredVersion(), + context.getResourcePlural(), client); + api.create(model).throwsApiException(); + return provider.get(objectClass, objectListClass, client, + context, model.getMetadata().getNamespace(), + model.getMetadata().getName()); + } + + /** + * Get the stubs for the objects in the given namespace that match + * the criteria from the given options. + * + * @param the object type + * @param the object list type + * @param the stub type + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the context + * @param namespace the namespace + * @param options the options + * @param provider the provider + * @return the collection + * @throws ApiException the api exception + */ + public static > + Collection list(Class objectClass, Class objectListClass, + K8sClient client, APIResource context, String namespace, + ListOptions options, SpecificSupplier provider) + throws ApiException { + var api = new GenericKubernetesApi<>(objectClass, objectListClass, + context.getGroup(), context.getPreferredVersion(), + context.getResourcePlural(), client); + var objs = api.list(namespace, options).throwsApiException(); + var result = new ArrayList(); + for (var item : objs.getObject().getItems()) { + result.add( + provider.get(client, namespace, item.getMetadata().getName())); + } + return result; + } + + /** + * Instantiates a new namespaced custom object stub. + * + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the context + * @param namespace the namespace + * @param name the name + */ + protected K8sGenericStub(Class objectClass, Class objectListClass, + K8sClient client, APIResource context, String namespace, + String name) { + this.client = client; + group = context.getGroup(); + version = context.getPreferredVersion(); + kind = context.getKind(); + plural = context.getResourcePlural(); + this.namespace = namespace; + this.name = name; + + Gson gson = client.getJSON().getGson(); + if (!checkAdapters(client)) { + client.getJSON().setGson(gson.newBuilder() + .registerTypeAdapterFactory( + new K8sDynamicModelTypeAdapterFactory()) + .create()); + } + api = new GenericKubernetesApi<>(objectClass, + objectListClass, group, version, plural, client); + } + + private boolean checkAdapters(ApiClient client) { + return K8sDynamicModelTypeAdapterFactory.K8sDynamicModelCreator.class + .equals(client.getJSON().getGson().getAdapter(K8sDynamicModel.class) + .getClass()) + && K8sDynamicModelTypeAdapterFactory.K8sDynamicModelsCreator.class + .equals(client.getJSON().getGson() + .getAdapter(K8sDynamicModels.class).getClass()); + } + + /** + * Gets the group. + * + * @return the group + */ + public String group() { + return group; + } + + /** + * Gets the version. + * + * @return the version + */ + public String version() { + return version; + } + + /** + * Gets the kind. + * + * @return the kind + */ + public String kind() { + return kind; + } + + /** + * Gets the plural. + * + * @return the plural + */ + public String plural() { + return plural; + } + + /** + * Gets the namespace. + * + * @return the namespace + */ + public String namespace() { + return namespace; + } + + /** + * Gets the name. + * + * @return the name + */ + public String name() { + return name; + } + + /** + * Delete the Kubernetes object. + * + * @throws ApiException the API exception + */ + public void delete() throws ApiException { + var result = api.delete(namespace, name); + if (result.isSuccess() + || result.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { + return; + } + result.throwsApiException(); + } + + /** + * Retrieves and returns the current state of the object. + * + * @return the object's state + * @throws ApiException the api exception + */ + public Optional model() throws ApiException { + return K8s.optional(api.get(namespace, name)); + } + + /** + * Updates the object's status. + * + * @param object the current state of the object (passed to `status`) + * @param status function that returns the new status + * @return the updated model or empty if not successful + * @throws ApiException the api exception + */ + public Optional updateStatus(O object, + Function status) throws ApiException { + return K8s.optional(api.updateStatus(object, status)); + } + + /** + * Updates the status. + * + * @param status the status + * @return the kubernetes api response + * the updated model or empty if not successful + * @throws ApiException the api exception + */ + public Optional updateStatus(Function status) + throws ApiException { + return updateStatus( + api.get(namespace, name).throwsApiException().getObject(), status); + } + + /** + * Patch the object. + * + * @param patchType the patch type + * @param patch the patch + * @param options the options + * @return the kubernetes api response + * @throws ApiException the api exception + */ + public Optional patch(String patchType, V1Patch patch, + PatchOptions options) throws ApiException { + return K8s + .optional(api.patch(namespace, name, patchType, patch, options)); + } + + /** + * Patch the object using default options. + * + * @param patchType the patch type + * @param patch the patch + * @return the kubernetes api response + * @throws ApiException the api exception + */ + public Optional + patch(String patchType, V1Patch patch) throws ApiException { + PatchOptions opts = new PatchOptions(); + return patch(patchType, patch, opts); + } + + /** + * A supplier for generic stubs. + * + * @param the object type + * @param the object list type + * @param the result type + */ + public interface GenericSupplier> { + + /** + * Gets a new stub. + * + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the API resource + * @param namespace the namespace + * @param name the name + * @return the result + */ + @SuppressWarnings("PMD.UseObjectForClearerAPI") + R get(Class objectClass, Class objectListClass, K8sClient client, + APIResource context, String namespace, String name); + } + + /** + * A supplier for specific stubs. + * + * @param the object type + * @param the object list type + * @param the result type + */ + public interface SpecificSupplier> { + + /** + * Gets a new stub. + * + * @param client the client + * @param namespace the namespace + * @param name the name + * @return the result + */ + R get(K8sClient client, String namespace, String name); + } + + @Override + @SuppressWarnings("PMD.UseLocaleWithCaseConversions") + public String toString() { + return (Strings.isNullOrEmpty(group) ? "" : group + "/") + + version.toUpperCase() + kind + " " + namespace + ":" + name; + } + +} diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ConfigMapStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ConfigMapStub.java new file mode 100644 index 0000000..58a9516 --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ConfigMapStub.java @@ -0,0 +1,60 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import io.kubernetes.client.Discovery.APIResource; +import io.kubernetes.client.openapi.models.V1ConfigMap; +import io.kubernetes.client.openapi.models.V1ConfigMapList; +import java.util.List; + +/** + * A stub for config maps (v1). + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class K8sV1ConfigMapStub + extends K8sGenericStub { + + /** + * Instantiates a new stub. + * + * @param client the client + * @param namespace the namespace + * @param name the name + */ + protected K8sV1ConfigMapStub(K8sClient client, String namespace, + String name) { + super(V1ConfigMap.class, V1ConfigMapList.class, client, + new APIResource("", List.of("v1"), "v1", "ConfigMap", true, + "configmaps", "configmap"), + namespace, name); + } + + /** + * Gets the stub for the given namespace and name. + * + * @param client the client + * @param namespace the namespace + * @param name the name + * @return the config map stub + */ + public static K8sV1ConfigMapStub get(K8sClient client, String namespace, + String name) { + return new K8sV1ConfigMapStub(client, namespace, name); + } +} \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1DeploymentStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1DeploymentStub.java new file mode 100644 index 0000000..049363d --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1DeploymentStub.java @@ -0,0 +1,77 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import io.kubernetes.client.Discovery.APIResource; +import io.kubernetes.client.custom.V1Patch; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.models.V1Deployment; +import io.kubernetes.client.openapi.models.V1DeploymentList; +import java.util.List; +import java.util.Optional; + +/** + * A stub for pods (v1). + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class K8sV1DeploymentStub + extends K8sGenericStub { + + /** + * Instantiates a new stub. + * + * @param client the client + * @param namespace the namespace + * @param name the name + */ + protected K8sV1DeploymentStub(K8sClient client, String namespace, + String name) { + super(V1Deployment.class, V1DeploymentList.class, client, + new APIResource("apps", List.of("v1"), "v1", "Pod", true, + "deployments", "deployment"), + namespace, name); + } + + /** + * Gets the stub for the given namespace and name. + * + * @param client the client + * @param namespace the namespace + * @param name the name + * @return the deployment stub + */ + public static K8sV1DeploymentStub get(K8sClient client, String namespace, + String name) { + return new K8sV1DeploymentStub(client, namespace, name); + } + + /** + * Scales the deployment. + * + * @param replicas the replicas + * @return the new model or empty if not successful + * @throws ApiException the API exception + */ + public Optional scale(int replicas) throws ApiException { + return patch(V1Patch.PATCH_FORMAT_JSON_PATCH, + new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/replicas" + + "\", \"value\": " + replicas + "}]"), + client.defaultPatchOptions()); + } +} \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1PodStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1PodStub.java new file mode 100644 index 0000000..fe47a0f --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1PodStub.java @@ -0,0 +1,78 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import io.kubernetes.client.Discovery.APIResource; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.models.V1Pod; +import io.kubernetes.client.openapi.models.V1PodList; +import io.kubernetes.client.util.generic.options.ListOptions; +import java.util.Collection; +import java.util.List; + +/** + * A stub for pods (v1). + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class K8sV1PodStub extends K8sGenericStub { + + public static final APIResource CONTEXT + = new APIResource("", List.of("v1"), "v1", "Pod", true, "pods", "pod"); + + /** + * Instantiates a new stub. + * + * @param client the client + * @param namespace the namespace + * @param name the name + */ + protected K8sV1PodStub(K8sClient client, String namespace, String name) { + super(V1Pod.class, V1PodList.class, client, CONTEXT, namespace, name); + } + + /** + * Gets the stub for the given namespace and name. + * + * @param client the client + * @param namespace the namespace + * @param name the name + * @return the kpod stub + */ + public static K8sV1PodStub get(K8sClient client, String namespace, + String name) { + return new K8sV1PodStub(client, namespace, name); + } + + /** + * Get the stubs for the objects in the given namespace that match + * the criteria from the given options. + * + * @param client the client + * @param namespace the namespace + * @param options the options + * @return the collection + * @throws ApiException the api exception + */ + public static Collection list(K8sClient client, + String namespace, ListOptions options) throws ApiException { + return K8sGenericStub.list(V1Pod.class, V1PodList.class, client, + CONTEXT, namespace, options, K8sV1PodStub::new); + } + +} \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1StatefulSetStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1StatefulSetStub.java new file mode 100644 index 0000000..13462b9 --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1StatefulSetStub.java @@ -0,0 +1,60 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import io.kubernetes.client.Discovery.APIResource; +import io.kubernetes.client.openapi.models.V1StatefulSet; +import io.kubernetes.client.openapi.models.V1StatefulSetList; +import java.util.List; + +/** + * A stub for stateful sets (v1). + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class K8sV1StatefulSetStub + extends K8sGenericStub { + + /** + * Instantiates a new stub. + * + * @param client the client + * @param namespace the namespace + * @param name the name + */ + protected K8sV1StatefulSetStub(K8sClient client, String namespace, + String name) { + super(V1StatefulSet.class, V1StatefulSetList.class, client, + new APIResource("apps", List.of("v1"), "v1", "StatefulSet", true, + "statefulsets", "statefulset"), + namespace, name); + } + + /** + * Gets the stub for the given namespace and name. + * + * @param client the client + * @param namespace the namespace + * @param name the name + * @return the stateful set stub + */ + public static K8sV1StatefulSetStub get(K8sClient client, String namespace, + String name) { + return new K8sV1StatefulSetStub(client, namespace, name); + } +} \ No newline at end of file diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmChannel.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmChannel.java index bc06e68..972693a 100644 --- a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmChannel.java +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmChannel.java @@ -18,8 +18,8 @@ package org.jdrupes.vmoperator.manager.events; -import io.kubernetes.client.openapi.ApiClient; -import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; +import org.jdrupes.vmoperator.common.K8sClient; +import org.jdrupes.vmoperator.common.K8sDynamicModel; import org.jgrapes.core.Channel; import org.jgrapes.core.EventPipeline; import org.jgrapes.core.Subchannel.DefaultSubchannel; @@ -31,8 +31,8 @@ import org.jgrapes.core.Subchannel.DefaultSubchannel; public class VmChannel extends DefaultSubchannel { private final EventPipeline pipeline; - private final ApiClient client; - private DynamicKubernetesObject vmDefinition; + private final K8sClient client; + private K8sDynamicModel vmDefinition; private long generation = -1; /** @@ -43,7 +43,7 @@ public class VmChannel extends DefaultSubchannel { * @param client the client */ public VmChannel(Channel mainChannel, EventPipeline pipeline, - ApiClient client) { + K8sClient client) { super(mainChannel); this.pipeline = pipeline; this.client = client; @@ -56,7 +56,7 @@ public class VmChannel extends DefaultSubchannel { * @return the watch channel */ @SuppressWarnings("PMD.LinguisticNaming") - public VmChannel setVmDefinition(DynamicKubernetesObject definition) { + public VmChannel setVmDefinition(K8sDynamicModel definition) { this.vmDefinition = definition; return this; } @@ -66,7 +66,7 @@ public class VmChannel extends DefaultSubchannel { * * @return the json object */ - public DynamicKubernetesObject vmDefinition() { + public K8sDynamicModel vmDefinition() { return vmDefinition; } @@ -109,7 +109,7 @@ public class VmChannel extends DefaultSubchannel { * * @return the API client */ - public ApiClient client() { + public K8sClient client() { return client; } } diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java index fd5d43c..e9c9ca1 100644 --- a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java @@ -19,7 +19,7 @@ package org.jdrupes.vmoperator.manager.events; import io.kubernetes.client.openapi.models.V1APIResource; -import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; +import org.jdrupes.vmoperator.common.K8sDynamicModel; import org.jgrapes.core.Channel; import org.jgrapes.core.Components; import org.jgrapes.core.Event; @@ -44,7 +44,7 @@ public class VmDefChanged extends Event { private final Type type; private final boolean specChanged; private final V1APIResource crd; - private final DynamicKubernetesObject vmDef; + private final K8sDynamicModel vmDef; /** * Instantiates a new VM changed event. @@ -55,7 +55,7 @@ public class VmDefChanged extends Event { * @param vmDefinition the VM definition */ public VmDefChanged(Type type, boolean specChanged, V1APIResource crd, - DynamicKubernetesObject vmDefinition) { + K8sDynamicModel vmDefinition) { this.type = type; this.specChanged = specChanged; this.crd = crd; @@ -92,7 +92,7 @@ public class VmDefChanged extends Event { * * @return the object. */ - public DynamicKubernetesObject vmDefinition() { + public K8sDynamicModel vmDefinition() { return vmDef; } diff --git a/org.jdrupes.vmoperator.manager/.settings/net.sf.jautodoc.prefs b/org.jdrupes.vmoperator.manager/.settings/net.sf.jautodoc.prefs index 6f3b6d4..03e8200 100644 --- a/org.jdrupes.vmoperator.manager/.settings/net.sf.jautodoc.prefs +++ b/org.jdrupes.vmoperator.manager/.settings/net.sf.jautodoc.prefs @@ -1,6 +1,6 @@ add_header=true eclipse.preferences.version=1 -header_text=/*\n * VM-Operator\n * Copyright (C) 2023 Michael N. Lipp\n * \n * This program is free software\: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n */ +header_text=/*\n * VM-Operator\n * Copyright (C) 2024 Michael N. Lipp\n * \n * This program is free software\: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n */ project_specific_settings=true replacements=\n\n\nReturns the\nSets the\nAdds the\nEdits the\nRemoves the\nInits the\nParses the\nCreates the\nBuilds the\nChecks if is\nPrints the\nChecks for\n\n\n visibility_package=false diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index d403be8..a4990d5 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -33,8 +33,6 @@ dependencies { runtimeOnly 'org.apache.logging.log4j:log4j-to-jul:2.20.0' runtimeOnly project(':org.jdrupes.vmoperator.vmconlet') - - testImplementation 'io.fabric8:kubernetes-client:[6.8.1,6.9)' } application { diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java index 589affc..ee693c2 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java @@ -18,20 +18,21 @@ package org.jdrupes.vmoperator.manager; +import io.kubernetes.client.apimachinery.GroupVersionKind; import io.kubernetes.client.custom.V1Patch; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.Configuration; -import io.kubernetes.client.util.Config; -import io.kubernetes.client.util.generic.options.PatchOptions; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.logging.Level; import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM; -import org.jdrupes.vmoperator.common.K8s; +import org.jdrupes.vmoperator.common.K8sClient; +import org.jdrupes.vmoperator.common.K8sDynamicStub; import org.jdrupes.vmoperator.manager.events.Exit; import org.jdrupes.vmoperator.manager.events.ModifyVm; +import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; @@ -160,35 +161,30 @@ public class Controller extends Component { * @throws IOException Signals that an I/O exception has occurred. */ @Handler - public void onModigyVm(ModifyVm event) throws ApiException, IOException { - patchVmSpec(event.name(), event.path(), event.value()); + public void onModifyVm(ModifyVm event, VmChannel channel) + throws ApiException, IOException { + patchVmSpec(channel.client(), event.name(), event.path(), + event.value()); } - private void patchVmSpec(String name, String path, Object value) + private void patchVmSpec(K8sClient client, String name, String path, + Object value) throws ApiException, IOException { - var crApi = K8s.crApi(Config.defaultClient(), VM_OP_GROUP, - VM_OP_KIND_VM, namespace, name); - if (crApi.isEmpty()) { - logger.warning(() -> "Trying to patch " + namespace + "/" + name - + " which does not exist."); - return; - } + var vmStub = K8sDynamicStub.get(client, + new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM), namespace, + name); // Patch running - PatchOptions patchOpts = new PatchOptions(); - patchOpts.setFieldManager("kubernetes-java-kubectl-apply"); String valueAsText = value instanceof String ? "\"" + value + "\"" : value.toString(); - var res = crApi.get().patch(namespace, name, - V1Patch.PATCH_FORMAT_JSON_PATCH, + var res = vmStub.patch(V1Patch.PATCH_FORMAT_JSON_PATCH, new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/" + path + "\", \"value\": " + valueAsText + "}]"), - patchOpts); - if (!res.isSuccess()) { + client.defaultPatchOptions()); + if (!res.isPresent()) { logger.warning( - () -> "Cannot patch pod annotations: " + res.getStatus()); + () -> "Cannot patch pod annotations for " + vmStub.name()); } - } } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java index efa95f4..85158c7 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/LoadBalancerReconciler.java @@ -29,11 +29,11 @@ import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; import io.kubernetes.client.util.generic.dynamic.Dynamics; import java.io.IOException; import java.io.StringWriter; -import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.logging.Logger; import org.jdrupes.vmoperator.common.K8s; +import org.jdrupes.vmoperator.common.K8sDynamicModel; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.util.GsonPtr; @@ -79,19 +79,25 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; Map model, VmChannel channel) throws IOException, TemplateException, ApiException { // Check if to be generated - @SuppressWarnings({ "unchecked", "PMD.AvoidDuplicateLiterals" }) - var lbs = Optional.of(model) + @SuppressWarnings({ "PMD.AvoidDuplicateLiterals", "unchecked" }) + var lbsDef = Optional.of(model) .map(m -> (Map) m.get("reconciler")) .map(c -> c.get(LOAD_BALANCER_SERVICE)).orElse(Boolean.FALSE); - if (lbs instanceof Boolean isOn && !isOn) { - return; - } - if (!(lbs instanceof Map)) { + if (!(lbsDef instanceof Map) && !(lbsDef instanceof Boolean)) { logger.warning(() -> "\"" + LOAD_BALANCER_SERVICE + "\" in configuration must be boolean or mapping but is " - + lbs.getClass() + "."); + + lbsDef.getClass() + "."); return; } + if (lbsDef instanceof Boolean isOn && !isOn) { + return; + } + JsonObject cfgMeta = new JsonObject(); + if (lbsDef instanceof Map) { + var json = channel.client().getJSON(); + cfgMeta + = json.deserialize(json.serialize(lbsDef), JsonObject.class); + } // Combine template and data and parse result var fmTemplate = fmConfig.getTemplate("runnerLoadBalancer.ftl.yaml"); @@ -101,7 +107,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; // https://github.com/kubernetes-client/java/issues/2741 var svcDef = Dynamics.newFromYaml( new Yaml(new SafeConstructor(new LoaderOptions())), out.toString()); - mergeMetadata(svcDef, lbs, channel); + mergeMetadata(svcDef, cfgMeta, event.vmDefinition()); // Apply DynamicKubernetesApi svcApi = new DynamicKubernetesApi("", "v1", @@ -109,20 +115,10 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; K8s.apply(svcApi, svcDef, svcDef.getRaw().toString()); } - @SuppressWarnings("unchecked") private void mergeMetadata(DynamicKubernetesObject svcDef, - Object lbsConfig, VmChannel channel) { - // Get metadata from config - Map asmData = Collections.emptyMap(); - if (lbsConfig instanceof Map config) { - asmData = (Map) config; - } - var json = channel.client().getJSON(); - JsonObject cfgMeta - = json.deserialize(json.serialize(asmData), JsonObject.class); - + JsonObject cfgMeta, K8sDynamicModel vmDefinition) { // Get metadata from VM definition - var vmMeta = GsonPtr.to(channel.vmDefinition().getRaw()).to("spec") + var vmMeta = GsonPtr.to(vmDefinition.data()).to("spec") .get(JsonObject.class, LOAD_BALANCER_SERVICE) .map(JsonObject::deepCopy).orElseGet(() -> new JsonObject()); diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index 2adb843..0683c76 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -44,6 +44,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import org.jdrupes.vmoperator.common.Convertions; +import org.jdrupes.vmoperator.common.K8sDynamicModel; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type; @@ -206,8 +207,8 @@ public class Reconciler extends Component { lbReconciler.reconcile(event, model, channel); } - private DynamicKubernetesObject patchCr(DynamicKubernetesObject vmDef) { - var json = vmDef.getRaw().deepCopy(); + private DynamicKubernetesObject patchCr(K8sDynamicModel vmDef) { + var json = vmDef.data().deepCopy(); // Adjust cdromImage path adjustCdRomPaths(json); diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java index 3cd6a39..8812a93 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java @@ -22,14 +22,13 @@ import freemarker.template.Configuration; import freemarker.template.TemplateException; 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.PatchOptions; import java.io.IOException; import java.io.StringWriter; import java.util.Map; import java.util.logging.Logger; -import org.jdrupes.vmoperator.common.K8s; +import org.jdrupes.vmoperator.common.K8sV1StatefulSetStub; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.util.GsonPtr; @@ -68,8 +67,6 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; public void reconcile(VmDefChanged event, Map model, VmChannel channel) throws IOException, TemplateException, ApiException { - DynamicKubernetesApi stsApi = new DynamicKubernetesApi("apps", "v1", - "statefulsets", channel.client()); var metadata = event.vmDefinition().getMetadata(); // Combine template and data and parse result @@ -83,25 +80,27 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; // If exists apply changes only when transitioning state // or not running. - var existing = K8s.get(stsApi, metadata); - if (existing.isPresent()) { - var current = GsonPtr.to(existing.get().getRaw()) - .to("spec").getAsInt("replicas").orElse(1); + var stsStub = K8sV1StatefulSetStub.get(channel.client(), + metadata.getNamespace(), metadata.getName()); + stsStub.model().ifPresent(sts -> { + var current = sts.getSpec().getReplicas(); var desired = GsonPtr.to(stsDef.getRaw()) .to("spec").getAsInt("replicas").orElse(1); if (current == 1 && desired == 1) { return; } - } + }); // Do apply changes PatchOptions opts = new PatchOptions(); opts.setForce(true); 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(); + if (stsStub.patch(V1Patch.PATCH_FORMAT_APPLY_YAML, + new V1Patch(channel.client().getJSON().serialize(stsDef)), opts) + .isEmpty()) { + logger.warning( + () -> "Could not patch stateful set for " + stsStub.name()); + } } } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java index 2d99727..c074ac2 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java @@ -1,6 +1,6 @@ /* * VM-Operator - * Copyright (C) 2023 Michael N. Lipp + * Copyright (C) 2023,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 @@ -21,6 +21,8 @@ package org.jdrupes.vmoperator.manager; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; +import io.kubernetes.client.apimachinery.GroupVersion; +import io.kubernetes.client.apimachinery.GroupVersionKind; import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.openapi.apis.ApisApi; @@ -33,7 +35,6 @@ import io.kubernetes.client.openapi.models.V1ObjectMeta; import io.kubernetes.client.util.Config; import io.kubernetes.client.util.Watch; 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.io.IOException; import java.nio.file.Files; @@ -48,7 +49,10 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; 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.K8sDynamicStub; +import org.jdrupes.vmoperator.common.K8sV1PodStub; 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; @@ -68,7 +72,7 @@ import org.jgrapes.util.events.ConfigurationUpdate; /** * Watches for changes of VM definitions. */ -@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports" }) public class VmWatcher extends Component { private String namespaceToWatch; @@ -269,13 +273,13 @@ public class VmWatcher extends Component { } private void handleVmDefinitionChange(V1APIResource vmsCrd, - Watch.Response vmDefStub) { - V1ObjectMeta metadata = vmDefStub.object.getMetadata(); + Watch.Response vmDefRef) throws ApiException { + V1ObjectMeta metadata = vmDefRef.object.getMetadata(); VmChannel channel = channels.computeIfAbsent(metadata.getName(), k -> { try { return new VmChannel(channel(), newEventPipeline(), - Config.defaultClient()); + new K8sClient()); } catch (IOException e) { logger.log(Level.SEVERE, e, () -> "Failed to create client" + " for handling changes: " + e.getMessage()); @@ -287,30 +291,27 @@ public class VmWatcher extends Component { } // Get full definition and associate with channel as backup - var apiVersion = K8s.version(vmDefStub.object.getApiVersion()); - DynamicKubernetesApi vmCrApi = new DynamicKubernetesApi(VM_OP_GROUP, - apiVersion, vmsCrd.getName(), channel.client()); - var curVmDef = K8s.get(vmCrApi, metadata); - curVmDef.ifPresent(def -> { - // Augment with "dynamic" data and associate with channel - addDynamicData(channel.client(), def); - channel.setVmDefinition(def); + @SuppressWarnings("PMD.ShortVariable") + var gv = GroupVersion.parse(vmDefRef.object.getApiVersion()); + var vmStub = K8sDynamicStub.get(channel.client(), + new GroupVersionKind(gv.getGroup(), gv.getVersion(), VM_OP_KIND_VM), + metadata.getNamespace(), metadata.getName()); + vmStub.model().ifPresent(vmDef -> { + addDynamicData(channel.client(), vmDef); + channel.setVmDefinition(vmDef); + + // Create and fire event + channel.pipeline().fire(new VmDefChanged(VmDefChanged.Type + .valueOf(vmDefRef.type), + channel + .setGeneration( + vmDefRef.object.getMetadata().getGeneration()), + vmsCrd, vmDef), channel); }); - - // Get eventual definition to use - var vmDef = curVmDef.orElse(channel.vmDefinition()); - - // Create and fire event - channel.pipeline().fire(new VmDefChanged(VmDefChanged.Type - .valueOf(vmDefStub.type), - channel - .setGeneration(vmDefStub.object.getMetadata().getGeneration()), - vmsCrd, vmDef), channel); } - private void addDynamicData(ApiClient client, - DynamicKubernetesObject vmDef) { - var rootNode = GsonPtr.to(vmDef.getRaw()).get(JsonObject.class); + private void addDynamicData(K8sClient client, K8sDynamicModel vmState) { + var rootNode = GsonPtr.to(vmState.data()).get(JsonObject.class); rootNode.addProperty("nodeName", ""); // VM definition status changes before the pod terminates. @@ -329,11 +330,18 @@ public class VmWatcher extends Component { var podSearch = new ListOptions(); podSearch.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + ",app.kubernetes.io/component=" + APP_NAME - + ",app.kubernetes.io/instance=" + vmDef.getMetadata().getName()); - var podList = K8s.podApi(client).list(namespaceToWatch, podSearch); - podList.getObject().getItems().stream().forEach(pod -> { - rootNode.addProperty("nodeName", pod.getSpec().getNodeName()); - }); + + ",app.kubernetes.io/instance=" + vmState.getMetadata().getName()); + try { + var podList + = K8sV1PodStub.list(client, namespaceToWatch, podSearch); + for (var podStub : podList) { + rootNode.addProperty("nodeName", + podStub.model().get().getSpec().getNodeName()); + } + } catch (ApiException e) { + logger.log(Level.WARNING, e, + () -> "Cannot access node information: " + e.getMessage()); + } } /** diff --git a/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java b/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java index 26eb387..13a93e1 100644 --- a/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java +++ b/org.jdrupes.vmoperator.manager/test/org/jdrupes/vmoperator/manager/BasicTests.java @@ -1,13 +1,18 @@ package org.jdrupes.vmoperator.manager; -import io.fabric8.kubernetes.client.Config; -import io.fabric8.kubernetes.client.KubernetesClient; -import io.fabric8.kubernetes.client.KubernetesClientBuilder; -import io.fabric8.kubernetes.client.dsl.base.ResourceDefinitionContext; +import io.kubernetes.client.Discovery.APIResource; +import io.kubernetes.client.openapi.ApiException; +import java.io.FileReader; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Map; +import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; +import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM; +import org.jdrupes.vmoperator.common.K8s; +import org.jdrupes.vmoperator.common.K8sClient; +import org.jdrupes.vmoperator.common.K8sDynamicStub; +import org.jdrupes.vmoperator.common.K8sV1ConfigMapStub; +import org.jdrupes.vmoperator.common.K8sV1DeploymentStub; +import org.jdrupes.vmoperator.common.K8sV1StatefulSetStub; import org.junit.jupiter.api.AfterAll; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.BeforeAll; @@ -18,8 +23,9 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; class BasicTests { - private static KubernetesClient client; - private static ResourceDefinitionContext vmsContext; + private static K8sClient client; + private static APIResource vmsContext; + private static K8sV1DeploymentStub mgrDeployment; @BeforeAll static void setUpBeforeClass() throws Exception { @@ -27,29 +33,27 @@ class BasicTests { assertNotNull(testCluster); // Get client - client = new KubernetesClientBuilder() - .withConfig(Config.autoConfigure(testCluster)).build(); + client = new K8sClient(); // Context for working with our CR - vmsContext = new ResourceDefinitionContext.Builder() - .withGroup("vmoperator.jdrupes.org").withKind("VirtualMachine") - .withPlural("vms").withNamespaced(true).withVersion("v1").build(); + var apiRes = K8s.context(client, VM_OP_GROUP, null, VM_OP_KIND_VM); + assertTrue(apiRes.isPresent()); + vmsContext = apiRes.get(); - // Cleanup - var resourcesInNamespace = client.genericKubernetesResources(vmsContext) - .inNamespace("vmop-dev"); - resourcesInNamespace.withName("unittest-vm").delete(); + // Cleanup existing VM + K8sDynamicStub.get(client, vmsContext, "vmop-dev", "unittest-vm") + .delete(); // Update manager pod by scaling deployment - client.apps().deployments().inNamespace("vmop-dev") - .withName("vm-operator").scale(0); - client.apps().deployments().inNamespace("vmop-dev") - .withName("vm-operator").scale(1); + mgrDeployment + = K8sV1DeploymentStub.get(client, "vmop-dev", "vm-operator"); + mgrDeployment.scale(0); + mgrDeployment.scale(1); // Wait until available + for (int i = 0; i < 10; i++) { - if (client.apps().deployments().inNamespace("vmop-dev") - .withName("vm-operator").get().getStatus().getConditions() + if (mgrDeployment.model().get().getStatus().getConditions() .stream().filter(c -> "Available".equals(c.getType())).findAny() .isPresent()) { return; @@ -62,44 +66,40 @@ class BasicTests { @AfterAll static void tearDownAfterClass() throws Exception { // Bring down manager - client.apps().deployments().inNamespace("vmop-dev") - .withName("vm-operator").scale(0); - client.close(); + mgrDeployment.scale(0); } @Test - void test() throws IOException, InterruptedException { + void test() throws IOException, InterruptedException, ApiException { // Load from Yaml - var vm = client.genericKubernetesResources(vmsContext) - .load(Files - .newInputStream(Path.of("test-resources/unittest-vm.yaml"))); - // Create Custom Resource - vm.create(); + var rdr = new FileReader("test-resources/unittest-vm.yaml"); + var vmStub = K8sDynamicStub.createFromYaml(client, vmsContext, rdr); + assertTrue(vmStub.model().isPresent()); // Wait for created resources - assertTrue(waitForConfigMap()); - assertTrue(waitForStatefulSet()); + assertTrue(waitForConfigMap(client)); + assertTrue(waitForStatefulSet(client)); // Check config map - var config = client.configMaps().inNamespace("vmop-dev") - .withName("unittest-vm").get(); + var config = K8sV1ConfigMapStub.get(client, "vmop-dev", "unittest-vm") + .model().get(); var yaml = new Yaml(new SafeConstructor(new LoaderOptions())) - .load((String) config.getData().get("config.yaml")); + .load(config.getData().get("config.yaml")); @SuppressWarnings("unchecked") - var currentRam = ((Map>>) yaml) + var maximumRam = ((Map>>) yaml) .get("/Runner").get("vm").get("maximumRam"); - assertEquals("4 GiB", currentRam); + assertEquals("4 GiB", maximumRam); // Cleanup - var resourcesInNamespace = client.genericKubernetesResources(vmsContext) - .inNamespace("vmop-dev"); - resourcesInNamespace.withName("unittest-vm").delete(); + K8sDynamicStub.get(client, vmsContext, "vmop-dev", "unittest-vm") + .delete(); } - private boolean waitForConfigMap() throws InterruptedException { + private boolean waitForConfigMap(K8sClient client) + throws InterruptedException, ApiException { + var stub = K8sV1ConfigMapStub.get(client, "vmop-dev", "unittest-vm"); for (int i = 0; i < 10; i++) { - if (client.configMaps().inNamespace("vmop-dev") - .withName("unittest-vm").get() != null) { + if (stub.model().isPresent()) { return true; } Thread.sleep(1000); @@ -107,10 +107,11 @@ class BasicTests { return false; } - private boolean waitForStatefulSet() throws InterruptedException { + private boolean waitForStatefulSet(K8sClient client) + throws InterruptedException, ApiException { + var stub = K8sV1StatefulSetStub.get(client, "vmop-dev", "unittest-vm"); for (int i = 0; i < 10; i++) { - if (client.apps().statefulSets().inNamespace("vmop-dev") - .withName("unittest-vm").get() != null) { + if (stub.model().isPresent()) { return true; } Thread.sleep(1000); diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle index 7179b8f..82525c6 100644 --- a/org.jdrupes.vmoperator.runner.qemu/build.gradle +++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle @@ -16,7 +16,7 @@ dependencies { implementation project(':org.jdrupes.vmoperator.common') implementation 'commons-cli:commons-cli:1.5.0' - implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:[2.15.1,3]' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:[2.16.1]' runtimeOnly 'org.slf4j:slf4j-jdk14:[2.0.7,3)' } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index 1cb5e74..d55e027 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -1,6 +1,6 @@ /* * VM-Operator - * Copyright (C) 2023 Michael N. Lipp + * Copyright (C) 2023,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 @@ -19,27 +19,17 @@ package org.jdrupes.vmoperator.runner.qemu; import com.google.gson.JsonObject; +import io.kubernetes.client.apimachinery.GroupVersionKind; import io.kubernetes.client.custom.Quantity; import io.kubernetes.client.custom.Quantity.Format; import io.kubernetes.client.custom.V1Patch; import io.kubernetes.client.openapi.ApiException; -import io.kubernetes.client.openapi.apis.ApisApi; -import io.kubernetes.client.openapi.apis.CustomObjectsApi; -import io.kubernetes.client.openapi.apis.EventsV1Api; import io.kubernetes.client.openapi.models.EventsV1Event; -import io.kubernetes.client.openapi.models.V1APIGroup; -import io.kubernetes.client.openapi.models.V1GroupVersionForDiscovery; -import io.kubernetes.client.openapi.models.V1ObjectMeta; -import io.kubernetes.client.util.Config; -import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; -import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; -import io.kubernetes.client.util.generic.options.PatchOptions; import java.io.IOException; import java.math.BigDecimal; import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; -import java.time.OffsetDateTime; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -48,6 +38,9 @@ import static org.jdrupes.vmoperator.common.Constants.APP_NAME; import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM; import org.jdrupes.vmoperator.common.K8s; +import org.jdrupes.vmoperator.common.K8sClient; +import org.jdrupes.vmoperator.common.K8sDynamicModel; +import org.jdrupes.vmoperator.common.K8sDynamicStub; import org.jdrupes.vmoperator.runner.qemu.events.BalloonChangeEvent; import org.jdrupes.vmoperator.runner.qemu.events.Exit; import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus; @@ -75,11 +68,11 @@ public class StatusUpdater extends Component { private String namespace; private String vmName; - private DynamicKubernetesApi vmCrApi; - private EventsV1Api evtsApi; + private K8sClient apiClient; private long observedGeneration; private boolean guestShutdownStops; private boolean shutdownByGuest; + private K8sDynamicStub vmStub; /** * Instantiates a new status updater. @@ -88,6 +81,16 @@ public class StatusUpdater extends Component { */ public StatusUpdater(Channel componentChannel) { super(componentChannel); + try { + apiClient = new K8sClient(); + io.kubernetes.client.openapi.Configuration + .setDefaultApiClient(apiClient); + } catch (IOException e) { + logger.log(Level.SEVERE, e, + () -> "Cannot access events API, terminating."); + fire(new Exit(1)); + } + } /** @@ -154,59 +157,18 @@ public class StatusUpdater extends Component { return; } try { - initVmCrApi(event); - } catch (IOException | ApiException e) { + vmStub = K8sDynamicStub.get(apiClient, + new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM), + namespace, vmName); + vmStub.model().ifPresent(model -> { + observedGeneration = model.getMetadata().getGeneration(); + }); + } catch (ApiException e) { logger.log(Level.SEVERE, e, - () -> "Cannot access VM's CR, terminating."); + () -> "Cannot access VM object, terminating."); event.cancel(true); fire(new Exit(1)); } - try { - evtsApi = new EventsV1Api(Config.defaultClient()); - } catch (IOException e) { - logger.log(Level.SEVERE, e, - () -> "Cannot access events API, terminating."); - event.cancel(true); - fire(new Exit(1)); - } - } - - private void initVmCrApi(Start event) throws IOException, ApiException { - var client = Config.defaultClient(); - var apis = new ApisApi(client).getAPIVersions(); - var crdVersions = apis.getGroups().stream() - .filter(g -> g.getName().equals(VM_OP_GROUP)).findFirst() - .map(V1APIGroup::getVersions).stream().flatMap(l -> l.stream()) - .map(V1GroupVersionForDiscovery::getVersion).toList(); - var coa = new CustomObjectsApi(client); - for (var crdVersion : crdVersions) { - var crdApiRes = coa.getAPIResources(VM_OP_GROUP, - crdVersion).getResources().stream() - .filter(r -> VM_OP_KIND_VM.equals(r.getKind())).findFirst(); - if (crdApiRes.isEmpty()) { - continue; - } - @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") - var crApi = new DynamicKubernetesApi(VM_OP_GROUP, - crdVersion, crdApiRes.get().getName(), client); - var vmCr = crApi.get(namespace, vmName); - if (vmCr.isSuccess()) { - vmCrApi = crApi; - observedGeneration - = vmCr.getObject().getMetadata().getGeneration(); - break; - } - } - if (vmCrApi == null) { - logger.severe(() -> "Cannot find VM's CR, terminating."); - event.cancel(true); - fire(new Exit(1)); - } - } - - @SuppressWarnings("PMD.AvoidDuplicateLiterals") - private JsonObject currentStatus(DynamicKubernetesObject vmCr) { - return vmCr.getRaw().getAsJsonObject("status").deepCopy(); } /** @@ -221,18 +183,19 @@ public class StatusUpdater extends Component { guestShutdownStops = event.configuration().guestShutdownStops; // Remainder applies only if we have a connection to k8s. - if (vmCrApi == null) { + if (vmStub == null) { return; } + // A change of the runner configuration is typically caused // by a new version of the CR. So we observe the new CR. - var vmCr = vmCrApi.get(namespace, vmName).throwsApiException() - .getObject(); - if (vmCr.getMetadata().getGeneration() == observedGeneration) { + var vmDef = vmStub.model(); + if (vmDef.isPresent() + && vmDef.get().metadata().getGeneration() == observedGeneration) { return; } - vmCrApi.updateStatus(vmCr, from -> { - JsonObject status = currentStatus(from); + vmStub.updateStatus(vmDef.get(), from -> { + JsonObject status = from.status(); status.getAsJsonArray("conditions").asList().stream() .map(cond -> (JsonObject) cond).filter(cond -> "Running" .equals(cond.get("type").getAsString())) @@ -249,15 +212,15 @@ public class StatusUpdater extends Component { * @throws ApiException */ @Handler + @SuppressWarnings("PMD.AssignmentInOperand") public void onRunnerStateChanged(RunnerStateChange event) throws ApiException { - if (vmCrApi == null) { + K8sDynamicModel vmDef; + if (vmStub == null || (vmDef = vmStub.model().orElse(null)) == null) { return; } - var vmCr = vmCrApi.get(namespace, vmName).throwsApiException() - .getObject(); - vmCrApi.updateStatus(vmCr, from -> { - JsonObject status = currentStatus(from); + vmStub.updateStatus(vmDef, from -> { + JsonObject status = from.status(); status.getAsJsonArray("conditions").asList().stream() .map(cond -> (JsonObject) cond) .forEach(cond -> { @@ -266,7 +229,7 @@ public class StatusUpdater extends Component { } }); if (event.state() == State.STARTING) { - status.addProperty("ram", GsonPtr.to(from.getRaw()) + status.addProperty("ram", GsonPtr.to(from.data()) .getAsString("spec", "vm", "maximumRam").orElse("0")); status.addProperty("cpus", 1); } else if (event.state() == State.STOPPED) { @@ -274,40 +237,32 @@ public class StatusUpdater extends Component { status.addProperty("cpus", 0); } return status; - }).throwsApiException(); + }); // Maybe stop VM if (event.state() == State.TERMINATING && !event.failed() && guestShutdownStops && shutdownByGuest) { logger.info(() -> "Stopping VM because of shutdown by guest."); - PatchOptions patchOpts = new PatchOptions(); - patchOpts.setFieldManager("kubernetes-java-kubectl-apply"); - var res = vmCrApi.patch(namespace, vmName, - V1Patch.PATCH_FORMAT_JSON_PATCH, + var res = vmStub.patch(V1Patch.PATCH_FORMAT_JSON_PATCH, new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/state" + "\", \"value\": \"Stopped\"}]"), - patchOpts); - if (!res.isSuccess()) { + apiClient.defaultPatchOptions()); + if (!res.isPresent()) { logger.warning( - () -> "Cannot patch pod annotations: " + res.getStatus()); + () -> "Cannot patch pod annotations for: " + vmStub.name()); } } // Log event - var evt = new EventsV1Event().kind("Event") - .metadata(new V1ObjectMeta().namespace(namespace) - .generateName("vmrunner-")) + var evt = new EventsV1Event() .reportingController(VM_OP_GROUP + "/" + APP_NAME) - .reportingInstance(vmCr.getMetadata().getName()) - .eventTime(OffsetDateTime.now()).type("Normal") - .regarding(K8s.objectReference(vmCr)) .action("StatusUpdate").reason(event.reason()) .note(event.message()); - evtsApi.createNamespacedEvent(namespace, evt, null, null, null, null); + K8s.createEvent(apiClient, vmDef, evt); } private void updateRunningCondition(RunnerStateChange event, - DynamicKubernetesObject from, JsonObject cond) { + K8sDynamicModel from, JsonObject cond) { boolean reportedRunning = "True".equals(cond.get("status").getAsString()); if (RUNNING_STATES.contains(event.state()) @@ -336,18 +291,16 @@ public class StatusUpdater extends Component { */ @Handler public void onBallonChange(BalloonChangeEvent event) throws ApiException { - if (vmCrApi == null) { + if (vmStub == null) { return; } - var vmCr = vmCrApi.get(namespace, vmName).throwsApiException() - .getObject(); - vmCrApi.updateStatus(vmCr, from -> { - JsonObject status = currentStatus(from); + vmStub.updateStatus(from -> { + JsonObject status = from.status(); status.addProperty("ram", new Quantity(new BigDecimal(event.size()), Format.BINARY_SI) .toSuffixedString()); return status; - }).throwsApiException(); + }); } /** @@ -358,16 +311,14 @@ public class StatusUpdater extends Component { */ @Handler public void onCpuChange(HotpluggableCpuStatus event) throws ApiException { - if (vmCrApi == null) { + if (vmStub == null) { return; } - var vmCr = vmCrApi.get(namespace, vmName).throwsApiException() - .getObject(); - vmCrApi.updateStatus(vmCr, from -> { - JsonObject status = currentStatus(from); + vmStub.updateStatus(from -> { + JsonObject status = from.status(); status.addProperty("cpus", event.usedCpus().size()); return status; - }).throwsApiException(); + }); } /** diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java index f096485..993a844 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java @@ -25,7 +25,6 @@ import freemarker.template.Template; import freemarker.template.TemplateNotFoundException; import io.kubernetes.client.custom.Quantity; import io.kubernetes.client.custom.Quantity.Format; -import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; @@ -38,6 +37,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.jdrupes.json.JsonBeanDecoder; import org.jdrupes.json.JsonDecodeException; +import org.jdrupes.vmoperator.common.K8sDynamicModel; import org.jdrupes.vmoperator.manager.events.ModifyVm; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; @@ -46,7 +46,6 @@ import org.jdrupes.vmoperator.util.GsonPtr; import org.jgrapes.core.Channel; import org.jgrapes.core.Event; import org.jgrapes.core.Manager; -import org.jgrapes.core.NamedChannel; import org.jgrapes.core.annotation.Handler; import org.jgrapes.webconsole.base.Conlet.RenderMode; import org.jgrapes.webconsole.base.ConletBaseModel; @@ -70,7 +69,9 @@ public class VmConlet extends FreeMarkerConlet { private static final Set MODES = RenderMode.asSet( RenderMode.Preview, RenderMode.View); - private final Map vmInfos + private final Map vmInfos + = new ConcurrentHashMap<>(); + private final Map vmChannels = new ConcurrentHashMap<>(); private final TimeSeries summarySeries = new TimeSeries(Duration.ofDays(1)); private Summary cachedSummary; @@ -162,7 +163,7 @@ public class VmConlet extends FreeMarkerConlet { } if (sendVmInfos) { for (var vmInfo : vmInfos.values()) { - var def = JsonBeanDecoder.create(vmInfo.getRaw().toString()) + var def = JsonBeanDecoder.create(vmInfo.data().toString()) .readObject(); channel.respond(new NotifyConletView(type(), conletId, "updateVm", def)); @@ -185,9 +186,10 @@ public class VmConlet extends FreeMarkerConlet { "PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals" }) public void onVmDefChanged(VmDefChanged event, VmChannel channel) throws JsonDecodeException, IOException { + var vmName = event.vmDefinition().getMetadata().getName(); if (event.type() == Type.DELETED) { - var vmName = event.vmDefinition().getMetadata().getName(); vmInfos.remove(vmName); + vmChannels.remove(vmName); for (var entry : conletIdsByConsoleConnection().entrySet()) { for (String conletId : entry.getValue()) { entry.getKey().respond(new NotifyConletView(type(), @@ -195,8 +197,11 @@ public class VmConlet extends FreeMarkerConlet { } } } else { - var vmDef = convertQuantities(event); - var def = JsonBeanDecoder.create(vmDef.getRaw().toString()) + var vmDef = new K8sDynamicModel(channel.client().getJSON() + .getGson(), convertQuantities(event.vmDefinition().data())); + vmInfos.put(vmName, vmDef); + vmChannels.put(vmName, channel); + var def = JsonBeanDecoder.create(vmDef.data().toString()) .readObject(); for (var entry : conletIdsByConsoleConnection().entrySet()) { for (String conletId : entry.getValue()) { @@ -217,28 +222,25 @@ public class VmConlet extends FreeMarkerConlet { } @SuppressWarnings("PMD.AvoidDuplicateLiterals") - private DynamicKubernetesObject convertQuantities(VmDefChanged event) { + private JsonObject convertQuantities(JsonObject vmDef) { // Clone and remove managed fields - var vmDef = new DynamicKubernetesObject( - event.vmDefinition().getRaw().deepCopy()); - GsonPtr.to(vmDef.getRaw()).to("metadata").get(JsonObject.class) + var json = vmDef.deepCopy(); + GsonPtr.to(json).to("metadata").get(JsonObject.class) .remove("managedFields"); // Convert RAM sizes to unitless numbers - var vmSpec = GsonPtr.to(vmDef.getRaw()).to("spec", "vm"); + var vmSpec = GsonPtr.to(json).to("spec", "vm"); vmSpec.set("maximumRam", Quantity.fromString( vmSpec.getAsString("maximumRam").orElse("0")).getNumber() .toBigInteger()); vmSpec.set("currentRam", Quantity.fromString( vmSpec.getAsString("currentRam").orElse("0")).getNumber() .toBigInteger()); - var status = GsonPtr.to(vmDef.getRaw()).to("status"); + var status = GsonPtr.to(json).to("status"); status.set("ram", Quantity.fromString( status.getAsString("ram").orElse("0")).getNumber() .toBigInteger()); - String vmName = event.vmDefinition().getMetadata().getName(); - vmInfos.put(vmName, vmDef); - return vmDef; + return json; } /** @@ -323,7 +325,7 @@ public class VmConlet extends FreeMarkerConlet { Summary summary = new Summary(); for (var vmDef : vmInfos.values()) { summary.totalVms += 1; - var status = GsonPtr.to(vmDef.getRaw()).to("status"); + var status = GsonPtr.to(vmDef.data()).to("status"); summary.usedCpus += status.getAsInt("cpus").orElse(0); summary.usedRam = summary.usedRam.add(status.getAsString("ram") .map(BigInteger::new).orElse(BigInteger.ZERO)); @@ -346,25 +348,28 @@ public class VmConlet extends FreeMarkerConlet { ConsoleConnection channel, VmsModel conletState) throws Exception { event.stop(); + var vmName = event.params().asString(0); + var vmChannel = vmChannels.get(vmName); + if (vmChannel == null) { + return; + } switch (event.method()) { case "start": - fire(new ModifyVm(event.params().asString(0), "state", "Running", - new NamedChannel("manager"))); + fire(new ModifyVm(vmName, "state", "Running", vmChannel)); break; case "stop": - fire(new ModifyVm(event.params().asString(0), "state", "Stopped", - new NamedChannel("manager"))); + fire(new ModifyVm(vmName, "state", "Stopped", vmChannel)); break; case "cpus": - fire(new ModifyVm(event.params().asString(0), "currentCpus", + fire(new ModifyVm(vmName, "currentCpus", new BigDecimal(event.params().asDouble(1)).toBigInteger(), - new NamedChannel("manager"))); + vmChannel)); break; case "ram": - fire(new ModifyVm(event.params().asString(0), "currentRam", + fire(new ModifyVm(vmName, "currentRam", new Quantity(new BigDecimal(event.params().asDouble(1)), Format.BINARY_SI).toSuffixedString(), - new NamedChannel("manager"))); + vmChannel)); break; default:// ignore break; From 85b0a160f36f5f16722753f5b3cc8399d0bbb414 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Mon, 18 Mar 2024 13:15:59 +0100 Subject: [PATCH 127/462] Generate doc on GitLab (#20) --- .gitlab-ci.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..70654cc --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,28 @@ +default: + # Template project: https://gitlab.com/pages/jekyll + # Docs: https://docs.gitlab.com/ee/pages/ + image: ruby:3.2 + before_script: + - git fetch origin gh-pages + - git checkout gh-pages + - gem install bundler + - bundle install +variables: + JEKYLL_ENV: production + LC_ALL: C.UTF-8 +test: + stage: test + script: + - bundle exec jekyll build -d test + artifacts: + paths: + - test + +pages: + stage: deploy + script: + - bundle exec jekyll build -d public + artifacts: + paths: + - public + environment: production From 3103452170ccddd8ff67bd9a60f50a8caae1366c Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Wed, 20 Mar 2024 11:03:09 +0100 Subject: [PATCH 128/462] Support for display secrets (#21) --- dev-example/test-vm-display-secret.yaml | 12 + .../jdrupes/vmoperator/common/Constants.java | 3 + .../org/jdrupes/vmoperator/common/K8s.java | 35 +- .../vmoperator/common/K8sDynamicStub.java | 58 ++- .../vmoperator/common/K8sGenericStub.java | 367 +++++++++--------- .../vmoperator/common/K8sObserver.java | 230 +++++++++++ .../vmoperator/common/K8sV1ConfigMapStub.java | 7 +- .../common/K8sV1DeploymentStub.java | 34 +- .../vmoperator/common/K8sV1PodStub.java | 13 +- .../vmoperator/common/K8sV1SecretStub.java | 60 +++ .../common/K8sV1StatefulSetStub.java | 9 +- .../manager/events/ChannelCache.java | 207 ++++++++++ .../manager/events/ChannelManager.java | 303 +++++++++++++++ .../manager/events/DisplaySecretChanged.java | 77 ++++ .../manager/events/VmDefChanged.java | 27 +- .../vmoperator/manager/runnerSts.ftl.yaml | 10 +- .../vmoperator/manager/AbstractMonitor.java | 287 ++++++++++++++ .../jdrupes/vmoperator/manager/Constants.java | 3 + .../vmoperator/manager/Controller.java | 18 +- .../manager/DisplaySecretsMonitor.java | 77 ++++ .../vmoperator/manager/Reconciler.java | 4 +- .../manager/StatefulSetReconciler.java | 8 + .../jdrupes/vmoperator/manager/VmMonitor.java | 183 +++++++++ .../jdrupes/vmoperator/manager/VmWatcher.java | 360 ----------------- .../display-password | 1 + .../runner/qemu/CdMediaController.java | 4 +- .../vmoperator/runner/qemu/Configuration.java | 13 +- .../vmoperator/runner/qemu/CpuController.java | 6 +- .../runner/qemu/DisplayController.java | 117 ++++++ .../vmoperator/runner/qemu/QemuMonitor.java | 14 +- .../vmoperator/runner/qemu/RamController.java | 4 +- .../vmoperator/runner/qemu/Runner.java | 49 ++- .../vmoperator/runner/qemu/StatusUpdater.java | 4 +- .../runner/qemu/commands/QmpCommand.java | 27 ++ .../qemu/commands/QmpSetDisplayPassword.java | 68 ++++ ...gurationUpdate.java => ConfigureQemu.java} | 4 +- .../templates/Standard-VM-latest.ftl.yaml | 6 +- .../jdrupes/vmoperator/vmconlet/VmConlet.java | 30 +- 38 files changed, 2081 insertions(+), 658 deletions(-) create mode 100644 dev-example/test-vm-display-secret.yaml create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sObserver.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java create mode 100644 org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ChannelCache.java create mode 100644 org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ChannelManager.java create mode 100644 org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/DisplaySecretChanged.java create mode 100644 org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/AbstractMonitor.java create mode 100644 org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretsMonitor.java create mode 100644 org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java delete mode 100644 org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java create mode 100644 org.jdrupes.vmoperator.runner.qemu/display-password create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpSetDisplayPassword.java rename org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/{RunnerConfigurationUpdate.java => ConfigureQemu.java} (93%) diff --git a/dev-example/test-vm-display-secret.yaml b/dev-example/test-vm-display-secret.yaml new file mode 100644 index 0000000..f3b5ccb --- /dev/null +++ b/dev-example/test-vm-display-secret.yaml @@ -0,0 +1,12 @@ +kind: Secret +apiVersion: v1 +metadata: + name: test-vm-display-secret + namespace: vmop-dev + labels: + app.kubernetes.io/name: vm-runner + app.kubernetes.io/instance: test-vm + app.kubernetes.io/component: display-secret +type: Opaque +data: + display-password: dGVzdC12bQ== diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java index 3ebe29d..d5d457c 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java @@ -26,6 +26,9 @@ public class Constants { /** The Constant APP_NAME. */ public static final String APP_NAME = "vm-runner"; + /** The Constant COMP_DISPLAY_SECRETS. */ + public static final String COMP_DISPLAY_SECRET = "display-secret"; + /** The Constant VM_OP_NAME. */ public static final String VM_OP_NAME = "vm-operator"; diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java index e350cf1..2c29fab 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java @@ -44,6 +44,7 @@ import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; +// TODO: Auto-generated Javadoc /** * Helpers for K8s API. */ @@ -74,6 +75,35 @@ public class K8s { return Optional.empty(); } + /** + * Returns a new context with the given version as preferred version. + * + * @param context the context + * @param version the version + * @return the API resource + */ + public static APIResource preferred(APIResource context, String version) { + assert context.getVersions().contains(version); + return new APIResource(context.getGroup(), + context.getVersions(), version, context.getKind(), + context.getNamespaced(), context.getResourcePlural(), + context.getResourceSingular()); + } + + /** + * Return a string representation of the context (API resource). + * + * @param context the context + * @return the string + */ + @SuppressWarnings("PMD.UseLocaleWithCaseConversions") + public static String toString(APIResource context) { + return (Strings.isNullOrEmpty(context.getGroup()) ? "" + : context.getGroup() + "/") + + context.getPreferredVersion().toUpperCase() + + context.getKind(); + } + /** * Convert Yaml to Json. * @@ -156,6 +186,7 @@ public class K8s { * @param api the api * @param existing the existing * @param update the update + * @return the t * @throws ApiException the api exception */ public static @@ -199,8 +230,10 @@ public class K8s { * * If `type` is not set, set it to "Normal" * * If `regarding` is not set, set it to the given object. * + * @param client the client + * @param object the object * @param event the event - * @throws ApiException + * @throws ApiException the api exception */ @SuppressWarnings("PMD.NPathComplexity") public static void createEvent(ApiClient client, diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStub.java index 1ab33ca..e6d36c5 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStub.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStub.java @@ -18,10 +18,14 @@ package org.jdrupes.vmoperator.common; +import com.google.gson.Gson; import io.kubernetes.client.Discovery.APIResource; import io.kubernetes.client.apimachinery.GroupVersionKind; +import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.util.generic.options.ListOptions; import java.io.Reader; +import java.util.Collection; /** * A stub for namespaced custom objects. It uses a dynamic model @@ -47,6 +51,24 @@ public class K8sDynamicStub Class objectListClass, K8sClient client, APIResource context, String namespace, String name) { super(objectClass, objectListClass, client, context, namespace, name); + + // Make sure that we have an adapter for our type + Gson gson = client.getJSON().getGson(); + if (!checkAdapters(client)) { + client.getJSON().setGson(gson.newBuilder() + .registerTypeAdapterFactory( + new K8sDynamicModelTypeAdapterFactory()) + .create()); + } + } + + private boolean checkAdapters(ApiClient client) { + return K8sDynamicModelTypeAdapterFactory.K8sDynamicModelCreator.class + .equals(client.getJSON().getGson().getAdapter(K8sDynamicModel.class) + .getClass()) + && K8sDynamicModelTypeAdapterFactory.K8sDynamicModelsCreator.class + .equals(client.getJSON().getGson() + .getAdapter(K8sDynamicModels.class).getClass()); } /** @@ -83,8 +105,7 @@ public class K8sDynamicStub @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", "PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" }) public static K8sDynamicStub get(K8sClient client, - APIResource context, String namespace, String name) - throws ApiException { + APIResource context, String namespace, String name) { return K8sGenericStub.get(K8sDynamicModel.class, K8sDynamicModels.class, client, context, namespace, name, K8sDynamicStub::new); } @@ -106,4 +127,37 @@ public class K8sDynamicStub K8sDynamicModels.class, client, context, model, K8sDynamicStub::new); } + + /** + * Get the stubs for the objects in the given namespace that match + * the criteria from the given options. + * + * @param client the client + * @param namespace the namespace + * @param options the options + * @return the collection + * @throws ApiException the api exception + */ + public static Collection list(K8sClient client, + APIResource context, String namespace, ListOptions options) + throws ApiException { + return K8sGenericStub.list(K8sDynamicModel.class, + K8sDynamicModels.class, client, context, namespace, options, + K8sDynamicStub::new); + } + + /** + * Get the stubs for the objects in the given namespace. + * + * @param client the client + * @param namespace the namespace + * @return the collection + * @throws ApiException the api exception + */ + public static Collection list(K8sClient client, + APIResource context, String namespace) + throws ApiException { + return list(client, context, namespace, new ListOptions()); + } + } \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sGenericStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sGenericStub.java index 30c6699..db68a38 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sGenericStub.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sGenericStub.java @@ -18,21 +18,22 @@ package org.jdrupes.vmoperator.common; -import com.google.gson.Gson; import io.kubernetes.client.Discovery.APIResource; import io.kubernetes.client.apimachinery.GroupVersionKind; import io.kubernetes.client.common.KubernetesListObject; import io.kubernetes.client.common.KubernetesObject; import io.kubernetes.client.custom.V1Patch; -import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.util.Strings; import io.kubernetes.client.util.generic.GenericKubernetesApi; +import io.kubernetes.client.util.generic.options.GetOptions; import io.kubernetes.client.util.generic.options.ListOptions; import io.kubernetes.client.util.generic.options.PatchOptions; import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedList; +import java.util.List; import java.util.Optional; import java.util.function.Function; @@ -49,145 +50,16 @@ public class K8sGenericStub { protected final K8sClient client; private final GenericKubernetesApi api; - protected final String group; - protected final String version; - protected final String kind; - protected final String plural; + protected final APIResource context; protected final String namespace; protected final String name; /** - * Get a namespaced object stub. If the version in parameter - * `gvk` is an empty string, the stub refers to the first object - * found with matching group and kind. - * - * @param the object type - * @param the object list type - * @param the stub type - * @param objectClass the object class - * @param objectListClass the object list class - * @param client the client - * @param gvk the group, version and kind - * @param namespace the namespace - * @param name the name - * @param provider the provider - * @return the stub if the object exists - * @throws ApiException the api exception - */ - @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", - "PMD.AvoidInstantiatingObjectsInLoops" }) - public static > - R get(Class objectClass, Class objectListClass, - K8sClient client, GroupVersionKind gvk, String namespace, - String name, GenericSupplier provider) - throws ApiException { - var context = K8s.context(client, gvk.getGroup(), gvk.getVersion(), - gvk.getKind()); - if (context.isEmpty()) { - throw new ApiException("No known API for " + gvk.getGroup() - + "/" + gvk.getVersion() + " " + gvk.getKind()); - } - return provider.get(objectClass, objectListClass, client, context.get(), - namespace, name); - } - - /** - * Get a namespaced object stub. - * - * @param the object type - * @param the object list type - * @param the stub type - * @param objectClass the object class - * @param objectListClass the object list class - * @param client the client - * @param context the context - * @param namespace the namespace - * @param name the name - * @param provider the provider - * @return the stub if the object exists - * @throws ApiException the api exception - */ - @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", - "PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" }) - public static > - R get(Class objectClass, Class objectListClass, - K8sClient client, APIResource context, String namespace, - String name, GenericSupplier provider) - throws ApiException { - return provider.get(objectClass, objectListClass, client, - context, namespace, name); - } - - /** - * Get a namespaced object stub for a newly created object. - * - * @param the object type - * @param the object list type - * @param the stub type - * @param objectClass the object class - * @param objectListClass the object list class - * @param client the client - * @param context the context - * @param model the model - * @param provider the provider - * @return the stub if the object exists - * @throws ApiException the api exception - */ - @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", - "PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" }) - public static > - R create(Class objectClass, Class objectListClass, - K8sClient client, APIResource context, O model, - GenericSupplier provider) throws ApiException { - var api = new GenericKubernetesApi<>(objectClass, objectListClass, - context.getGroup(), context.getPreferredVersion(), - context.getResourcePlural(), client); - api.create(model).throwsApiException(); - return provider.get(objectClass, objectListClass, client, - context, model.getMetadata().getNamespace(), - model.getMetadata().getName()); - } - - /** - * Get the stubs for the objects in the given namespace that match - * the criteria from the given options. - * - * @param the object type - * @param the object list type - * @param the stub type - * @param objectClass the object class - * @param objectListClass the object list class - * @param client the client - * @param context the context - * @param namespace the namespace - * @param options the options - * @param provider the provider - * @return the collection - * @throws ApiException the api exception - */ - public static > - Collection list(Class objectClass, Class objectListClass, - K8sClient client, APIResource context, String namespace, - ListOptions options, SpecificSupplier provider) - throws ApiException { - var api = new GenericKubernetesApi<>(objectClass, objectListClass, - context.getGroup(), context.getPreferredVersion(), - context.getResourcePlural(), client); - var objs = api.list(namespace, options).throwsApiException(); - var result = new ArrayList(); - for (var item : objs.getObject().getItems()) { - result.add( - provider.get(client, namespace, item.getMetadata().getName())); - } - return result; - } - - /** - * Instantiates a new namespaced custom object stub. + * Instantiates a new stub for the object specified. If the object + * exists in the context specified, the version (see + * {@link #version()} is bound to the existing object's version. + * Else the stub is dangling with the version set to the context's + * preferred version. * * @param objectClass the object class * @param objectListClass the object list class @@ -196,35 +68,47 @@ public class K8sGenericStub objectClass, Class objectListClass, K8sClient client, APIResource context, String namespace, String name) { this.client = client; - group = context.getGroup(); - version = context.getPreferredVersion(); - kind = context.getKind(); - plural = context.getResourcePlural(); this.namespace = namespace; this.name = name; - Gson gson = client.getJSON().getGson(); - if (!checkAdapters(client)) { - client.getJSON().setGson(gson.newBuilder() - .registerTypeAdapterFactory( - new K8sDynamicModelTypeAdapterFactory()) - .create()); + // Bind version + var foundVersion = context.getPreferredVersion(); + GenericKubernetesApi testApi = null; + GetOptions mdOpts + = new GetOptions().isPartialObjectMetadataRequest(true); + for (var version : candidateVersions(context)) { + testApi = new GenericKubernetesApi<>(objectClass, objectListClass, + context.getGroup(), version, context.getResourcePlural(), + client); + if (testApi.get(namespace, name, mdOpts) + .isSuccess()) { + foundVersion = version; + break; + } } - api = new GenericKubernetesApi<>(objectClass, - objectListClass, group, version, plural, client); + if (foundVersion.equals(context.getPreferredVersion())) { + this.context = context; + } else { + this.context = K8s.preferred(context, foundVersion); + } + + api = Optional.ofNullable(testApi) + .orElseGet(() -> new GenericKubernetesApi<>(objectClass, + objectListClass, group(), version(), plural(), client)); } - private boolean checkAdapters(ApiClient client) { - return K8sDynamicModelTypeAdapterFactory.K8sDynamicModelCreator.class - .equals(client.getJSON().getGson().getAdapter(K8sDynamicModel.class) - .getClass()) - && K8sDynamicModelTypeAdapterFactory.K8sDynamicModelsCreator.class - .equals(client.getJSON().getGson() - .getAdapter(K8sDynamicModels.class).getClass()); + /** + * Gets the context. + * + * @return the context + */ + public APIResource context() { + return context; } /** @@ -233,7 +117,7 @@ public class K8sGenericStub the object type - * @param the object list type - * @param the result type - */ - public interface SpecificSupplier> { - - /** - * Gets a new stub. - * - * @param client the client - * @param namespace the namespace - * @param name the name - * @return the result - */ - R get(K8sClient client, String namespace, String name); - } - @Override @SuppressWarnings("PMD.UseLocaleWithCaseConversions") public String toString() { - return (Strings.isNullOrEmpty(group) ? "" : group + "/") - + version.toUpperCase() + kind + " " + namespace + ":" + name; + return (Strings.isNullOrEmpty(group()) ? "" : group() + "/") + + version().toUpperCase() + kind() + " " + namespace + ":" + name; + } + + /** + * Get a namespaced object stub. If the version in parameter + * `gvk` is an empty string, the stub refers to the first object + * found with matching group and kind. + * + * @param the object type + * @param the object list type + * @param the stub type + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param gvk the group, version and kind + * @param namespace the namespace + * @param name the name + * @param provider the provider + * @return the stub if the object exists + * @throws ApiException the api exception + */ + @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop" }) + public static > + R get(Class objectClass, Class objectListClass, + K8sClient client, GroupVersionKind gvk, String namespace, + String name, GenericSupplier provider) + throws ApiException { + var context = K8s.context(client, gvk.getGroup(), gvk.getVersion(), + gvk.getKind()); + if (context.isEmpty()) { + throw new ApiException("No known API for " + gvk.getGroup() + + "/" + gvk.getVersion() + " " + gvk.getKind()); + } + return provider.get(objectClass, objectListClass, client, context.get(), + namespace, name); + } + + /** + * Get a namespaced object stub. + * + * @param the object type + * @param the object list type + * @param the stub type + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the context + * @param namespace the namespace + * @param name the name + * @param provider the provider + * @return the stub if the object exists + * @throws ApiException the api exception + */ + @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", + "PMD.UseObjectForClearerAPI" }) + public static > + R get(Class objectClass, Class objectListClass, + K8sClient client, APIResource context, String namespace, + String name, GenericSupplier provider) { + return provider.get(objectClass, objectListClass, client, + context, namespace, name); + } + + /** + * Get a namespaced object stub for a newly created object. + * + * @param the object type + * @param the object list type + * @param the stub type + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the context + * @param model the model + * @param provider the provider + * @return the stub if the object exists + * @throws ApiException the api exception + */ + @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", + "PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" }) + public static > + R create(Class objectClass, Class objectListClass, + K8sClient client, APIResource context, O model, + GenericSupplier provider) throws ApiException { + var api = new GenericKubernetesApi<>(objectClass, objectListClass, + context.getGroup(), context.getPreferredVersion(), + context.getResourcePlural(), client); + api.create(model).throwsApiException(); + return provider.get(objectClass, objectListClass, client, + context, model.getMetadata().getNamespace(), + model.getMetadata().getName()); + } + + /** + * Get the stubs for the objects in the given namespace that match + * the criteria from the given options. + * + * @param the object type + * @param the object list type + * @param the stub type + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the context + * @param namespace the namespace + * @param options the options + * @param provider the provider + * @return the collection + * @throws ApiException the api exception + */ + public static > + Collection list(Class objectClass, Class objectListClass, + K8sClient client, APIResource context, String namespace, + ListOptions options, GenericSupplier provider) + throws ApiException { + var result = new ArrayList(); + for (var version : candidateVersions(context)) { + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + var api = new GenericKubernetesApi<>(objectClass, objectListClass, + context.getGroup(), version, context.getResourcePlural(), + client); + var objs = api.list(namespace, options).throwsApiException(); + for (var item : objs.getObject().getItems()) { + result.add(provider.get(objectClass, objectListClass, client, + context, namespace, item.getMetadata().getName())); + } + } + return result; + } + + private static List candidateVersions(APIResource context) { + var result = new LinkedList<>(context.getVersions()); + result.remove(context.getPreferredVersion()); + result.add(0, context.getPreferredVersion()); + return result; } } diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sObserver.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sObserver.java new file mode 100644 index 0000000..148ce83 --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sObserver.java @@ -0,0 +1,230 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import io.kubernetes.client.Discovery.APIResource; +import io.kubernetes.client.common.KubernetesListObject; +import io.kubernetes.client.common.KubernetesObject; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.util.Watch.Response; +import io.kubernetes.client.util.generic.GenericKubernetesApi; +import io.kubernetes.client.util.generic.options.ListOptions; +import java.time.Duration; +import java.time.Instant; +import java.util.function.BiConsumer; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * An observer that watches namespaced resources in a given context and + * invokes a handler on changes. + * + * @param the object type for the context + * @param the object list type for the context + */ +public class K8sObserver { + + /** + * The type of change reported by {@link Response} as enum. + */ + public enum ResponseType { + ADDED, MODIFIED, DELETED + } + + @SuppressWarnings("PMD.FieldNamingConventions") + protected final Logger logger = Logger.getLogger(getClass().getName()); + + protected final K8sClient client; + protected final GenericKubernetesApi api; + protected final APIResource context; + protected final String namespace; + protected final ListOptions options; + protected final Thread thread; + protected BiConsumer> handler; + protected BiConsumer, Throwable> onTerminated; + + /** + * Create and start a new observer for objects in the given context + * (using preferred version) and namespace with the given options. + * + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the context + * @param namespace the namespace + * @param options the options + */ + @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", + "PMD.UseObjectForClearerAPI", "PMD.AvoidCatchingThrowable", + "PMD.CognitiveComplexity" }) + public K8sObserver(Class objectClass, Class objectListClass, + K8sClient client, APIResource context, String namespace, + ListOptions options) { + this.client = client; + this.context = context; + this.namespace = namespace; + this.options = options; + + api = new GenericKubernetesApi<>(objectClass, objectListClass, + context.getGroup(), context.getPreferredVersion(), + context.getResourcePlural(), client); + thread = new Thread(() -> { + try { + logger.info(() -> "Watching " + context.getResourcePlural() + + " (" + context.getPreferredVersion() + ")" + + " in " + namespace); + + // Watch sometimes terminates without apparent reason. + while (!Thread.currentThread().isInterrupted()) { + Instant startedAt = Instant.now(); + try { + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + var changed = api.watch(namespace, options).iterator(); + while (changed.hasNext()) { + handler.accept(client, changed.next()); + } + } catch (ApiException e) { + logger.log(Level.FINE, e, () -> "Problem watching" + + " (will retry): " + e.getMessage()); + delayRestart(startedAt); + } + } + if (onTerminated != null) { + onTerminated.accept(this, null); + } + } catch (Throwable e) { + logger.log(Level.SEVERE, e, () -> "Probem watching: " + + e.getMessage()); + if (onTerminated != null) { + onTerminated.accept(this, e); + } + } + }); + thread.setDaemon(true); + } + + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") + private void delayRestart(Instant started) { + var runningFor = Duration + .between(started, Instant.now()).toMillis(); + if (runningFor < 5000) { + logger.log(Level.FINE, () -> "Waiting... "); + try { + Thread.sleep(5000 - runningFor); + } catch (InterruptedException e1) { // NOPMD + // Retry + } + logger.log(Level.FINE, () -> "Retrying"); + } + } + + /** + * Sets the handler. + * + * @param handler the handler + * @return the observer + */ + public K8sObserver + handler(BiConsumer> handler) { + this.handler = handler; + return this; + } + + /** + * Sets a function to invoke if the observer terminates. First argument + * is this observer, the second is the throwable that caused the + * abnormal termination or `null` if the observer was terminated + * by {@link #stop()}. + * + * @param onTerminated the on terminated + * @return the observer + */ + public K8sObserver onTerminated( + BiConsumer, Throwable> onTerminated) { + this.onTerminated = onTerminated; + return this; + } + + /** + * Start the observer. + * + * @return the observer + */ + public K8sObserver start() { + if (handler == null) { + throw new IllegalStateException("No handler defined"); + } + thread.start(); + return this; + } + + /** + * Stops the observer. + * + * @return the observer + */ + public K8sObserver stop() { + thread.interrupt(); + return this; + } + + /** + * Returns the client. + * + * @return the client + */ + public K8sClient client() { + return client; + } + + /** + * Returns the context. + * + * @return the context + */ + public APIResource context() { + return context; + } + + /** + * Returns the observed namespace. + * + * @return the namespace + */ + public String getNamespace() { + return namespace; + } + + /** + * Returns the options for object selection. + * + * @return the list options + */ + public ListOptions options() { + return options; + } + + @Override + @SuppressWarnings("PMD.UseLocaleWithCaseConversions") + public String toString() { + return "Observer for " + K8s.toString(context) + " " + namespace; + } + +} diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ConfigMapStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ConfigMapStub.java index 58a9516..c85726e 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ConfigMapStub.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ConfigMapStub.java @@ -30,6 +30,9 @@ import java.util.List; public class K8sV1ConfigMapStub extends K8sGenericStub { + public static final APIResource CONTEXT = new APIResource("", List.of("v1"), + "v1", "ConfigMap", true, "configmaps", "configmap"); + /** * Instantiates a new stub. * @@ -40,9 +43,7 @@ public class K8sV1ConfigMapStub protected K8sV1ConfigMapStub(K8sClient client, String namespace, String name) { super(V1ConfigMap.class, V1ConfigMapList.class, client, - new APIResource("", List.of("v1"), "v1", "ConfigMap", true, - "configmaps", "configmap"), - namespace, name); + CONTEXT, namespace, name); } /** diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1DeploymentStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1DeploymentStub.java index 049363d..16e5c82 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1DeploymentStub.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1DeploymentStub.java @@ -33,6 +33,10 @@ import java.util.Optional; public class K8sV1DeploymentStub extends K8sGenericStub { + /** The deployment's context. */ + public static final APIResource CONTEXT = new APIResource("apps", + List.of("v1"), "v1", "Pod", true, "deployments", "deployment"); + /** * Instantiates a new stub. * @@ -43,22 +47,7 @@ public class K8sV1DeploymentStub protected K8sV1DeploymentStub(K8sClient client, String namespace, String name) { super(V1Deployment.class, V1DeploymentList.class, client, - new APIResource("apps", List.of("v1"), "v1", "Pod", true, - "deployments", "deployment"), - namespace, name); - } - - /** - * Gets the stub for the given namespace and name. - * - * @param client the client - * @param namespace the namespace - * @param name the name - * @return the deployment stub - */ - public static K8sV1DeploymentStub get(K8sClient client, String namespace, - String name) { - return new K8sV1DeploymentStub(client, namespace, name); + CONTEXT, namespace, name); } /** @@ -74,4 +63,17 @@ public class K8sV1DeploymentStub + "\", \"value\": " + replicas + "}]"), client.defaultPatchOptions()); } + + /** + * Gets the stub for the given namespace and name. + * + * @param client the client + * @param namespace the namespace + * @param name the name + * @return the deployment stub + */ + public static K8sV1DeploymentStub get(K8sClient client, String namespace, + String name) { + return new K8sV1DeploymentStub(client, namespace, name); + } } \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1PodStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1PodStub.java index fe47a0f..b3da776 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1PodStub.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1PodStub.java @@ -32,6 +32,7 @@ import java.util.List; @SuppressWarnings("PMD.DataflowAnomalyAnalysis") public class K8sV1PodStub extends K8sGenericStub { + /** The pods' context. */ public static final APIResource CONTEXT = new APIResource("", List.of("v1"), "v1", "Pod", true, "pods", "pod"); @@ -72,7 +73,17 @@ public class K8sV1PodStub extends K8sGenericStub { public static Collection list(K8sClient client, String namespace, ListOptions options) throws ApiException { return K8sGenericStub.list(V1Pod.class, V1PodList.class, client, - CONTEXT, namespace, options, K8sV1PodStub::new); + CONTEXT, namespace, options, K8sV1PodStub::getGeneric); + } + + /** + * Provide {@link GenericSupplier}. + */ + @SuppressWarnings("PMD.UnusedFormalParameter") + private static K8sV1PodStub getGeneric(Class objectClass, + Class objectListClass, K8sClient client, + APIResource context, String namespace, String name) { + return new K8sV1PodStub(client, namespace, name); } } \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java new file mode 100644 index 0000000..d2fcaf3 --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java @@ -0,0 +1,60 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import io.kubernetes.client.Discovery.APIResource; +import io.kubernetes.client.openapi.models.V1Secret; +import io.kubernetes.client.openapi.models.V1SecretList; +import java.util.List; + +/** + * A stub for secrets (v1). + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class K8sV1SecretStub extends K8sGenericStub { + + public static final APIResource CONTEXT = new APIResource("", List.of("v1"), + "v1", "Secret", true, "secrets", "secret"); + + /** + * Instantiates a new stub. + * + * @param client the client + * @param namespace the namespace + * @param name the name + */ + protected K8sV1SecretStub(K8sClient client, String namespace, + String name) { + super(V1Secret.class, V1SecretList.class, client, + CONTEXT, namespace, name); + } + + /** + * Gets the stub for the given namespace and name. + * + * @param client the client + * @param namespace the namespace + * @param name the name + * @return the config map stub + */ + public static K8sV1SecretStub get(K8sClient client, String namespace, + String name) { + return new K8sV1SecretStub(client, namespace, name); + } +} \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1StatefulSetStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1StatefulSetStub.java index 13462b9..b918725 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1StatefulSetStub.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1StatefulSetStub.java @@ -30,6 +30,11 @@ import java.util.List; public class K8sV1StatefulSetStub extends K8sGenericStub { + /** The stateful sets' context */ + public static final APIResource CONTEXT + = new APIResource("apps", List.of("v1"), "v1", "StatefulSet", true, + "statefulsets", "statefulset"); + /** * Instantiates a new stub. * @@ -39,9 +44,7 @@ public class K8sV1StatefulSetStub */ protected K8sV1StatefulSetStub(K8sClient client, String namespace, String name) { - super(V1StatefulSet.class, V1StatefulSetList.class, client, - new APIResource("apps", List.of("v1"), "v1", "StatefulSet", true, - "statefulsets", "statefulset"), + super(V1StatefulSet.class, V1StatefulSetList.class, client, CONTEXT, namespace, name); } diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ChannelCache.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ChannelCache.java new file mode 100644 index 0000000..fc8df7a --- /dev/null +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ChannelCache.java @@ -0,0 +1,207 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.manager.events; + +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import org.jgrapes.core.Channel; + +/** + * A channel manager that tracks mappings from a key to a channel using + * "add/remove" (or "open/close") events and the channels on which they + * are delivered. + * + * @param the key type + * @param the channel type + * @param the type of the associated data + */ +public class ChannelCache { + + private final Map> channels = new ConcurrentHashMap<>(); + + /** + * Helper + */ + @SuppressWarnings("PMD.ShortClassName") + private static class Data { + public WeakReference channel; + public A associated; + + /** + * Instantiates a new value. + * + * @param channel the channel + */ + public Data(C channel) { + this.channel = new WeakReference<>(channel); + } + } + + /** + * Combines the channel and the associated data. + * + * @param the generic type + * @param the generic type + */ + @SuppressWarnings("PMD.ShortClassName") + public static class Both { + + /** The channel. */ + public C channel; + + /** The associated. */ + public A associated; + + /** + * Instantiates a new both. + * + * @param channel the channel + * @param associated the associated + */ + public Both(C channel, A associated) { + super(); + this.channel = channel; + this.associated = associated; + } + } + + /** + * Returns the channel and associates data registered for the key + * or an empty optional if no mapping exists. + * + * @param key the key + * @return the result + */ + public Optional> both(K key) { + synchronized (channels) { + var value = channels.get(key); + if (value == null) { + return Optional.empty(); + } + var channel = value.channel.get(); + if (channel == null) { + // Cleanup old reference + channels.remove(key); + return Optional.empty(); + } + return Optional.of(new Both<>(channel, value.associated)); + } + } + + /** + * Store the given data. + * + * @param key the key + * @param channel the channel + * @param associated the associated + * @return the channel manager + */ + public ChannelCache put(K key, C channel, A associated) { + Data data = new Data<>(channel); + data.associated = associated; + channels.put(key, data); + return this; + } + + /** + * Store the given data. + * + * @param key the key + * @param channel the channel + * @return the channel manager + */ + public ChannelCache put(K key, C channel) { + put(key, channel, null); + return this; + } + + /** + * Returns the channel registered for the key or an empty optional + * if no mapping exists. + * + * @param key the key + * @return the optional + */ + public Optional channel(K key) { + return both(key).map(b -> b.channel); + } + + /** + * Associate the entry for the channel with the given data. The entry + * for the channel must already exist. + * + * @param key the key + * @param data the data + * @return the channel manager + */ + public ChannelCache associate(K key, A data) { + synchronized (channels) { + Optional.ofNullable(channels.get(key)) + .ifPresent(v -> v.associated = data); + } + return this; + } + + /** + * Return the data associated with the entry for the channel. + * + * @param key the key + * @return the data + */ + public Optional associated(K key) { + return both(key).map(b -> b.associated); + } + + /** + * Returns all associated data. + * + * @return the collection + */ + public Collection associated() { + synchronized (channels) { + return channels.values().stream() + .filter(v -> v.channel.get() != null && v.associated != null) + .map(v -> v.associated).toList(); + } + } + + /** + * Removes the channel with the given name. + * + * @param name the name + */ + public void remove(String name) { + synchronized (channels) { + channels.remove(name); + } + } + + /** + * Returns all known keys. + * + * @return the sets the + */ + public Set keys() { + return channels.keySet(); + } +} diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ChannelManager.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ChannelManager.java new file mode 100644 index 0000000..eb27ea0 --- /dev/null +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ChannelManager.java @@ -0,0 +1,303 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.manager.events; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import org.jgrapes.core.Channel; + +/** + * A channel manager that maintains mappings from a key to a channel. + * As a convenience, it is possible to additionally associate arbitrary + * data with the entry (and thus with the channel). + * + * The manager should be used by a component that defines channels for + * housekeeping. It can be shared between this component and another + * component, preferably using the {@link #fixed()} view for the + * second component. Alternatively, the second component can use a + * {@link ChannelCache} to track the mappings using events. + * + * @param the key type + * @param the channel type + * @param the type of the associated data + */ +public class ChannelManager { + + private final Map> channels = new ConcurrentHashMap<>(); + private final Function supplier; + private ChannelManager readOnly; + + /** + * Combines the channel and the associated data. + * + * @param the generic type + * @param the generic type + */ + @SuppressWarnings("PMD.ShortClassName") + public static class Both { + + /** The channel. */ + public C channel; + + /** The associated. */ + public A associated; + + /** + * Instantiates a new both. + * + * @param channel the channel + * @param associated the associated + */ + public Both(C channel, A associated) { + super(); + this.channel = channel; + this.associated = associated; + } + } + + /** + * Instantiates a new channel manager. + * + * @param supplier the supplier that creates new channels + */ + public ChannelManager(Function supplier) { + this.supplier = supplier; + } + + /** + * Instantiates a new channel manager without a default supplier. + */ + public ChannelManager() { + this(k -> null); + } + + /** + * Returns the channel and associates data registered for the key + * or an empty optional if no mapping exists. + * + * @param key the key + * @return the result + */ + public Optional> both(K key) { + synchronized (channels) { + return Optional.ofNullable(channels.get(key)); + } + } + + /** + * Store the given data. + * + * @param key the key + * @param channel the channel + * @param associated the associated + * @return the channel manager + */ + public ChannelManager put(K key, C channel, A associated) { + channels.put(key, new Both<>(channel, associated)); + return this; + } + + /** + * Store the given data. + * + * @param key the key + * @param channel the channel + * @return the channel manager + */ + public ChannelManager put(K key, C channel) { + put(key, channel, null); + return this; + } + + /** + * Returns the channel registered for the key or an empty optional + * if no mapping exists. + * + * @param key the key + * @return the optional + */ + public Optional channel(K key) { + return both(key).map(b -> b.channel); + } + + /** + * Returns the {@link Channel} for the given name, creating it using + * the supplier passed to the constructor if it doesn't exist yet. + * + * @param key the key + * @return the channel + */ + public Optional getChannel(K key) { + return getChannel(key, supplier); + } + + /** + * Returns the {@link Channel} for the given name, creating it using + * the given supplier if it doesn't exist yet. + * + * @param key the key + * @param supplier the supplier + * @return the channel + */ + @SuppressWarnings({ "PMD.AssignmentInOperand", + "PMD.DataflowAnomalyAnalysis" }) + public Optional getChannel(K key, Function supplier) { + synchronized (channels) { + return Optional + .of(Optional.ofNullable(channels.get(key)) + .map(v -> v.channel) + .orElseGet(() -> { + var channel = supplier.apply(key); + channels.put(key, new Both<>(channel, null)); + return channel; + })); + } + } + + /** + * Associate the entry for the channel with the given data. The entry + * for the channel must already exist. + * + * @param key the key + * @param data the data + * @return the channel manager + */ + public ChannelManager associate(K key, A data) { + synchronized (channels) { + Optional.ofNullable(channels.get(key)) + .ifPresent(v -> v.associated = data); + } + return this; + } + + /** + * Return the data associated with the entry for the channel. + * + * @param key the key + * @return the data + */ + public Optional associated(K key) { + return both(key).map(b -> b.associated); + } + + /** + * Returns all associated data. + * + * @return the collection + */ + public Collection associated() { + synchronized (channels) { + return channels.values().stream() + .filter(v -> v.associated != null) + .map(v -> v.associated).toList(); + } + } + + /** + * Removes the channel with the given name. + * + * @param name the name + */ + public void remove(String name) { + synchronized (channels) { + channels.remove(name); + } + } + + /** + * Returns all known keys. + * + * @return the sets the + */ + public Set keys() { + return channels.keySet(); + } + + /** + * Returns a read only view of this channel manager. The methods + * that usually create a new entry refrain from doing so. The + * methods that change the value of channel and {@link #remove(String)} + * do nothing. The associated data, however, can still be changed. + * + * @return the channel manager + */ + public ChannelManager fixed() { + if (readOnly == null) { + readOnly = new ChannelManager<>(supplier) { + + @Override + public Optional> both(K key) { + return ChannelManager.this.both(key); + } + + @Override + public ChannelManager put(K key, C channel, + A associated) { + return associate(key, associated); + } + + @Override + public Optional getChannel(K key) { + return ChannelManager.this.channel(key); + } + + @Override + public Optional getChannel(K key, Function supplier) { + return ChannelManager.this.channel(key); + } + + @Override + public ChannelManager associate(K key, A data) { + return ChannelManager.this.associate(key, data); + } + + @Override + public Optional associated(K key) { + return ChannelManager.this.associated(key); + } + + @Override + public Collection associated() { + return ChannelManager.this.associated(); + } + + @Override + public void remove(String name) { + // Do nothing + } + + @Override + public Set keys() { + return ChannelManager.this.keys(); + } + + @Override + public ChannelManager fixed() { + return ChannelManager.this.fixed(); + } + + }; + } + return readOnly; + } +} diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/DisplaySecretChanged.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/DisplaySecretChanged.java new file mode 100644 index 0000000..69dabe6 --- /dev/null +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/DisplaySecretChanged.java @@ -0,0 +1,77 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.manager.events; + +import io.kubernetes.client.openapi.models.V1Secret; +import org.jdrupes.vmoperator.common.K8sObserver.ResponseType; +import org.jgrapes.core.Channel; +import org.jgrapes.core.Components; +import org.jgrapes.core.Event; + +/** + * Indicates that a display secret has changed. + */ +@SuppressWarnings("PMD.DataClass") +public class DisplaySecretChanged extends Event { + + private final ResponseType type; + private final V1Secret secret; + + /** + * Initializes a new display secret changed event. + * + * @param type the type + * @param secret the secret + */ + public DisplaySecretChanged(ResponseType type, V1Secret secret) { + this.type = type; + this.secret = secret; + } + + /** + * Returns the type. + * + * @return the type + */ + public ResponseType type() { + return type; + } + + /** + * Gets the secret. + * + * @return the secret + */ + public V1Secret secret() { + return secret; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(Components.objectName(this)).append(" [") + .append(secret.getMetadata().getName()).append(' ').append(type); + if (channels() != null) { + builder.append(", channels="); + builder.append(Channel.toString(channels())); + } + builder.append(']'); + return builder.toString(); + } +} diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java index e9c9ca1..d4a1aa9 100644 --- a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java @@ -18,8 +18,8 @@ package org.jdrupes.vmoperator.manager.events; -import io.kubernetes.client.openapi.models.V1APIResource; import org.jdrupes.vmoperator.common.K8sDynamicModel; +import org.jdrupes.vmoperator.common.K8sObserver; import org.jgrapes.core.Channel; import org.jgrapes.core.Components; import org.jgrapes.core.Event; @@ -34,16 +34,8 @@ import org.jgrapes.core.Event; @SuppressWarnings("PMD.DataClass") public class VmDefChanged extends Event { - /** - * The type of change. - */ - public enum Type { - ADDED, MODIFIED, DELETED - } - - private final Type type; + private final K8sObserver.ResponseType type; private final boolean specChanged; - private final V1APIResource crd; private final K8sDynamicModel vmDef; /** @@ -51,14 +43,12 @@ public class VmDefChanged extends Event { * * @param type the type * @param specChanged the spec part changed - * @param crd the crd * @param vmDefinition the VM definition */ - public VmDefChanged(Type type, boolean specChanged, V1APIResource crd, + public VmDefChanged(K8sObserver.ResponseType type, boolean specChanged, K8sDynamicModel vmDefinition) { this.type = type; this.specChanged = specChanged; - this.crd = crd; this.vmDef = vmDefinition; } @@ -67,7 +57,7 @@ public class VmDefChanged extends Event { * * @return the type */ - public Type type() { + public K8sObserver.ResponseType type() { return type; } @@ -78,15 +68,6 @@ public class VmDefChanged extends Event { return specChanged; } - /** - * Returns the Crd. - * - * @return the v 1 API resource - */ - public V1APIResource crd() { - return crd; - } - /** * Returns the object. * diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml index 2e5712b..3d4a316 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerSts.ftl.yaml @@ -126,8 +126,14 @@ spec: # hostPath: # path: /sys/fs/cgroup - name: config - configMap: - name: ${ cr.metadata.name.asString } + projected: + sources: + - configMap: + name: ${ cr.metadata.name.asString } + <#if displaySecret??> + - secret: + name: ${ displaySecret } + - name: vmop-image-repository persistentVolumeClaim: claimName: vmop-image-repository diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/AbstractMonitor.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/AbstractMonitor.java new file mode 100644 index 0000000..e78a5e0 --- /dev/null +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/AbstractMonitor.java @@ -0,0 +1,287 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.manager; + +import io.kubernetes.client.Discovery.APIResource; +import io.kubernetes.client.common.KubernetesListObject; +import io.kubernetes.client.common.KubernetesObject; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.util.Watch.Response; +import io.kubernetes.client.util.generic.options.ListOptions; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import org.jdrupes.vmoperator.common.K8s; +import org.jdrupes.vmoperator.common.K8sClient; +import org.jdrupes.vmoperator.common.K8sObserver; +import org.jdrupes.vmoperator.common.K8sObserver.ResponseType; +import org.jdrupes.vmoperator.manager.events.ChannelManager; +import org.jdrupes.vmoperator.manager.events.Exit; +import org.jgrapes.core.Channel; +import org.jgrapes.core.Component; +import org.jgrapes.core.Components; +import org.jgrapes.core.annotation.Handler; +import org.jgrapes.core.events.Start; +import org.jgrapes.core.events.Stop; +import org.jgrapes.util.events.ConfigurationUpdate; + +/** + * A base class for monitoring VM related resources. + * + * @param the object type for the context + * @param the object list type for the context + */ +@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis" }) +public abstract class AbstractMonitor extends Component { + + private final Class objectClass; + private final Class objectListClass; + private K8sClient client; + private APIResource context; + private String namespace; + private ListOptions options = new ListOptions(); + private final AtomicInteger observerCounter = new AtomicInteger(0); + private ChannelManager channelManager; + private boolean channelManagerMaster; + + /** + * Initializes the instance. + * + * @param componentChannel the component channel + */ + protected AbstractMonitor(Channel componentChannel, Class objectClass, + Class objectListClass) { + super(componentChannel); + this.objectClass = objectClass; + this.objectListClass = objectListClass; + } + + /** + * Return the client. + * + * @return the client + */ + public K8sClient client() { + return client; + } + + /** + * Sets the client to be used. + * + * @param client the client + * @return the abstract monitor + */ + public AbstractMonitor client(K8sClient client) { + this.client = client; + return this; + } + + /** + * Return the observed namespace. + * + * @return the namespace + */ + public String namespace() { + return namespace; + } + + /** + * Sets the namespace to be observed. + * + * @param namespace the namespaceToWatch to set + * @return the abstract monitor + */ + public AbstractMonitor namespace(String namespace) { + this.namespace = namespace; + return this; + } + + /** + * Returns the options for selecting the objects to observe. + * + * @return the options + */ + public ListOptions options() { + return options; + } + + /** + * Sets the options for selecting the objects to observe. + * + * @param options the options to set + * @return the abstract monitor + */ + public AbstractMonitor options(ListOptions options) { + this.options = options; + return this; + } + + /** + * Returns the observed context. + * + * @return the context + */ + public APIResource context() { + return context; + } + + /** + * Sets the context to observe. + * + * @param context the context + * @return the abstract monitor + */ + public AbstractMonitor context(APIResource context) { + this.context = context; + return this; + } + + /** + * Returns the channel manager. + * + * @return the context + */ + public ChannelManager channelManager() { + return channelManager; + } + + /** + * Sets the channel manager. + * + * @param channelManager the channel manager + * @return the abstract monitor + */ + public AbstractMonitor + channelManager(ChannelManager channelManager) { + this.channelManager = channelManager; + return this; + } + + /** + * Looks for a key "namespace" in the configuration and, if found, + * sets the namespace to its value. + * + * @param event the event + */ + @Handler + public void onConfigurationUpdate(ConfigurationUpdate event) { + event.structured(Components.manager(parent()).componentPath()) + .ifPresent(c -> { + if (c.containsKey("namespace")) { + namespace = (String) c.get("namespace"); + } + }); + } + + /** + * Handle the start event. Configures the namespace invokes + * {@link #prepareMonitoring()} and starts the observers. + * + * @param event the event + */ + @Handler(priority = 10) + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + public void onStart(Start event) { + try { + // Get namespace + if (namespace == null) { + var path = Path + .of("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); + if (Files.isReadable(path)) { + namespace + = Files.lines(path).findFirst().orElse(null); + } + } + + // Additional preparations by derived class + prepareMonitoring(); + assert client != null; + assert context != null; + assert namespace != null; + logger.fine(() -> "Observing " + K8s.toString(context) + + " objects in " + namespace); + + // Monitor all versions + for (var version : context.getVersions()) { + createObserver(version); + } + registerAsGenerator(); + } catch (IOException | ApiException e) { + logger.log(Level.SEVERE, e, + () -> "Cannot watch VMs, terminating."); + event.cancel(true); + fire(new Exit(1)); + } + } + + private void createObserver(String version) { + observerCounter.incrementAndGet(); + new K8sObserver<>(objectClass, objectListClass, client, + K8s.preferred(context, version), namespace, options) + .handler((c, r) -> { + handleChange(c, r); + if (ResponseType.valueOf(r.type) == ResponseType.DELETED + && channelManagerMaster) { + channelManager.remove(r.object.getMetadata().getName()); + } + }).onTerminated((o, t) -> { + if (observerCounter.decrementAndGet() == 0) { + unregisterAsGenerator(); + } + // Exception has been logged already + if (t != null) { + fire(new Stop()); + } + }).start(); + } + + /** + * Invoked by {@link #onStart(Start)} after the namespace has + * been configured and before starting the observer. + * + * @throws IOException Signals that an I/O exception has occurred. + * @throws ApiException the api exception + */ + @SuppressWarnings("PMD.EmptyMethodInAbstractClassShouldBeAbstract") + protected void prepareMonitoring() throws IOException, ApiException { + // To be overridden by derived class. + } + + /** + * Handle an observed change. + * + * @param client the client + * @param change the change + */ + protected abstract void handleChange(K8sClient client, Response change); + + /** + * Returns the {@link Channel} for the given name. + * + * @param name the name + * @return the channel used for events related to the specified object + */ + protected Optional channel(String name) { + return channelManager.getChannel(name); + } +} diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java index 2ef4199..17e74bf 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java @@ -23,6 +23,9 @@ package org.jdrupes.vmoperator.manager; */ 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 STATE_RUNNING. */ public static final String STATE_RUNNING = "Running"; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java index ee693c2..4b8e5aa 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java @@ -30,6 +30,7 @@ import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM; import org.jdrupes.vmoperator.common.K8sClient; import org.jdrupes.vmoperator.common.K8sDynamicStub; +import org.jdrupes.vmoperator.manager.events.ChannelManager; import org.jdrupes.vmoperator.manager.events.Exit; import org.jdrupes.vmoperator.manager.events.ModifyVm; import org.jdrupes.vmoperator.manager.events.VmChannel; @@ -46,7 +47,7 @@ import org.jgrapes.util.events.ConfigurationUpdate; * [Operator Whitepaper](https://github.com/cncf/tag-app-delivery/blob/eece8f7307f2970f46f100f51932db106db46968/operator-wg/whitepaper/Operator-WhitePaper_v1-0.md#operator-components-in-kubernetes). * * The implementation splits the controller in two components. The - * {@link VmWatcher} and the {@link Reconciler}. The former watches + * {@link VmMonitor} and the {@link Reconciler}. The former watches * the VM definitions (CRs) and generates {@link VmDefChanged} events * when they change. The latter handles the changes and reconciles the * resources in the cluster. @@ -87,7 +88,20 @@ public class Controller extends Component { public Controller(Channel componentChannel) { super(componentChannel); // Prepare component tree - attach(new VmWatcher(channel())); + ChannelManager chanMgr + = new ChannelManager<>(name -> { + try { + return new VmChannel(channel(), newEventPipeline(), + new K8sClient()); + } catch (IOException e) { + logger.log(Level.SEVERE, e, () -> "Failed to create client" + + " for handling changes: " + e.getMessage()); + return null; + } + }); + attach(new VmMonitor(channel()).channelManager(chanMgr)); + attach(new DisplaySecretsMonitor(channel()) + .channelManager(chanMgr.fixed())); attach(new Reconciler(channel())); } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretsMonitor.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretsMonitor.java new file mode 100644 index 0000000..de51080 --- /dev/null +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretsMonitor.java @@ -0,0 +1,77 @@ +/* + * 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 . + */ + +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.DisplaySecretChanged; +import org.jdrupes.vmoperator.manager.events.VmChannel; +import org.jgrapes.core.Channel; + +/** + * Watches for changes of display secrets. + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class DisplaySecretsMonitor + extends AbstractMonitor { + + /** + * Instantiates a new display secrets monitor. + * + * @param componentChannel the component channel + */ + public DisplaySecretsMonitor(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 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 DisplaySecretChanged( + ResponseType.valueOf(change.type), change.object), channel); + } + +} diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index 0683c76..07944cf 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -45,9 +45,9 @@ import java.util.Map; import java.util.Optional; import org.jdrupes.vmoperator.common.Convertions; import org.jdrupes.vmoperator.common.K8sDynamicModel; +import org.jdrupes.vmoperator.common.K8sObserver; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; -import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type; import org.jdrupes.vmoperator.util.ExtendedObjectWrapper; import org.jdrupes.vmoperator.util.GsonPtr; import org.jgrapes.core.Channel; @@ -194,7 +194,7 @@ public class Reconciler extends Component { // Ownership relationships takes care of deletions var defMeta = event.vmDefinition().getMetadata(); - if (event.type() == Type.DELETED) { + if (event.type() == K8sObserver.ResponseType.DELETED) { logger.fine(() -> "VM \"" + defMeta.getName() + "\" deleted"); return; } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java index 8812a93..e642502 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.io.StringWriter; import java.util.Map; import java.util.logging.Logger; +import org.jdrupes.vmoperator.common.K8sV1SecretStub; import org.jdrupes.vmoperator.common.K8sV1StatefulSetStub; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; @@ -69,6 +70,13 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; throws IOException, TemplateException, ApiException { var metadata = event.vmDefinition().getMetadata(); + // Check if we have a display secret + var dsStub = K8sV1SecretStub.get(channel.client(), + metadata.getNamespace(), metadata.getName() + "-display-secret"); + dsStub.model().ifPresent(m -> { + model.put("displaySecret", m.getMetadata().getName()); + }); + // Combine template and data and parse result var fmTemplate = fmConfig.getTemplate("runnerSts.ftl.yaml"); StringWriter out = new StringWriter(); diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java new file mode 100644 index 0000000..b12dc48 --- /dev/null +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java @@ -0,0 +1,183 @@ +/* + * VM-Operator + * Copyright (C) 2023,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 . + */ + +package org.jdrupes.vmoperator.manager; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.models.V1ObjectMeta; +import io.kubernetes.client.util.Watch; +import io.kubernetes.client.util.generic.options.ListOptions; +import java.io.IOException; +import java.util.Set; +import java.util.logging.Level; +import java.util.stream.Collectors; +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 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; +import org.jdrupes.vmoperator.manager.events.VmChannel; +import org.jdrupes.vmoperator.manager.events.VmDefChanged; +import org.jdrupes.vmoperator.util.GsonPtr; +import org.jgrapes.core.Channel; + +/** + * Watches for changes of VM definitions. + */ +@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports" }) +public class VmMonitor + extends AbstractMonitor { + + /** + * Instantiates a new VM definition watcher. + * + * @param componentChannel the component channel + */ + public VmMonitor(Channel componentChannel) { + super(componentChannel, K8sDynamicModel.class, K8sDynamicModels.class); + } + + @Override + protected void prepareMonitoring() throws IOException, ApiException { + client(new K8sClient()); + + // Get all our API versions + var ctx = K8s.context(client(), VM_OP_GROUP, "", VM_OP_KIND_VM); + if (ctx.isEmpty()) { + logger.severe(() -> "Cannot get CRD context."); + return; + } + context(ctx.get()); + + // Remove left over resources + purge(); + } + + @SuppressWarnings("PMD.CognitiveComplexity") + private void purge() throws ApiException { + // Get existing CRs (VMs) + var known = K8sDynamicStub.list(client(), context(), namespace()) + .stream().map(stub -> stub.name()).collect(Collectors.toSet()); + ListOptions opts = new ListOptions(); + opts.setLabelSelector( + "app.kubernetes.io/managed-by=" + VM_OP_NAME + "," + + "app.kubernetes.io/name=" + APP_NAME); + for (var context : Set.of(K8sV1StatefulSetStub.CONTEXT, + K8sV1ConfigMapStub.CONTEXT)) { + for (var resStub : K8sDynamicStub.list(client(), context, + namespace(), opts)) { + String instance = resStub.model() + .map(m -> m.metadata().getName()).orElse("(unknown)"); + if (!known.contains(instance)) { + resStub.delete(); + } + } + } + } + + @Override + protected void handleChange(K8sClient client, + Watch.Response response) { + V1ObjectMeta metadata = response.object.getMetadata(); + VmChannel channel = channel(metadata.getName()).orElse(null); + if (channel == null) { + return; + } + + // Get full definition and associate with channel as backup + var vmDef = response.object; + if (vmDef.data() == null) { + // ADDED event does not provide data, see + // https://github.com/kubernetes-client/java/issues/3215 + vmDef = getModel(client, vmDef); + } + if (vmDef.data() != null) { + // New data, augment and save + addDynamicData(channel.client(), vmDef); + channel.setVmDefinition(vmDef); + } else { + // Reuse cached + vmDef = channel.vmDefinition(); + } + if (vmDef == null) { + logger.warning( + () -> "Cannot get model for " + response.object.getMetadata()); + return; + } + + // Create and fire event + channel.pipeline() + .fire(new VmDefChanged(ResponseType.valueOf(response.type), + channel.setGeneration( + response.object.getMetadata().getGeneration()), + vmDef), channel); + } + + private K8sDynamicModel getModel(K8sClient client, K8sDynamicModel vmDef) { + try { + return K8sDynamicStub.get(client, context(), namespace(), + vmDef.metadata().getName()).model().orElse(null); + } catch (ApiException e) { + return null; + } + } + + private void addDynamicData(K8sClient client, K8sDynamicModel vmState) { + var rootNode = GsonPtr.to(vmState.data()).get(JsonObject.class); + rootNode.addProperty("nodeName", ""); + + // VM definition status changes before the pod terminates. + // This results in pod information being shown for a stopped + // VM which is irritating. So check condition first. + var isRunning = GsonPtr.to(rootNode).to("status", "conditions") + .get(JsonArray.class) + .asList().stream().filter(el -> "Running" + .equals(((JsonObject) el).get("type").getAsString())) + .findFirst().map(el -> "True" + .equals(((JsonObject) el).get("status").getAsString())) + .orElse(false); + if (!isRunning) { + return; + } + var podSearch = new ListOptions(); + podSearch.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + + ",app.kubernetes.io/component=" + APP_NAME + + ",app.kubernetes.io/instance=" + vmState.getMetadata().getName()); + try { + var podList + = K8sV1PodStub.list(client, namespace(), podSearch); + for (var podStub : podList) { + rootNode.addProperty("nodeName", + podStub.model().get().getSpec().getNodeName()); + } + } catch (ApiException e) { + logger.log(Level.WARNING, e, + () -> "Cannot access node information: " + e.getMessage()); + } + } +} diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java deleted file mode 100644 index c074ac2..0000000 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmWatcher.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * VM-Operator - * Copyright (C) 2023,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 . - */ - -package org.jdrupes.vmoperator.manager; - -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.reflect.TypeToken; -import io.kubernetes.client.apimachinery.GroupVersion; -import io.kubernetes.client.apimachinery.GroupVersionKind; -import io.kubernetes.client.openapi.ApiClient; -import io.kubernetes.client.openapi.ApiException; -import io.kubernetes.client.openapi.apis.ApisApi; -import io.kubernetes.client.openapi.apis.CustomObjectsApi; -import io.kubernetes.client.openapi.models.V1APIGroup; -import io.kubernetes.client.openapi.models.V1APIResource; -import io.kubernetes.client.openapi.models.V1GroupVersionForDiscovery; -import io.kubernetes.client.openapi.models.V1Namespace; -import io.kubernetes.client.openapi.models.V1ObjectMeta; -import io.kubernetes.client.util.Config; -import io.kubernetes.client.util.Watch; -import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi; -import io.kubernetes.client.util.generic.options.ListOptions; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Duration; -import java.time.Instant; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Level; -import static org.jdrupes.vmoperator.common.Constants.VM_OP_GROUP; -import org.jdrupes.vmoperator.common.K8sClient; -import org.jdrupes.vmoperator.common.K8sDynamicModel; -import org.jdrupes.vmoperator.common.K8sDynamicStub; -import org.jdrupes.vmoperator.common.K8sV1PodStub; -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; -import org.jdrupes.vmoperator.manager.events.Exit; -import org.jdrupes.vmoperator.manager.events.VmChannel; -import org.jdrupes.vmoperator.manager.events.VmDefChanged; -import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type; -import org.jdrupes.vmoperator.util.GsonPtr; -import org.jgrapes.core.Channel; -import org.jgrapes.core.Component; -import org.jgrapes.core.Components; -import org.jgrapes.core.annotation.Handler; -import org.jgrapes.core.events.Start; -import org.jgrapes.core.events.Stop; -import org.jgrapes.util.events.ConfigurationUpdate; - -/** - * Watches for changes of VM definitions. - */ -@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports" }) -public class VmWatcher extends Component { - - private String namespaceToWatch; - private final Map channels = new ConcurrentHashMap<>(); - - /** - * Instantiates a new VM definition watcher. - * - * @param componentChannel the component channel - */ - public VmWatcher(Channel componentChannel) { - super(componentChannel); - } - - /** - * Configure the component. - * - * @param event the event - */ - @Handler - public void onConfigurationUpdate(ConfigurationUpdate event) { - event.structured(Components.manager(parent()).componentPath()) - .ifPresent(c -> { - if (c.containsKey("namespace")) { - namespaceToWatch = (String) c.get("namespace"); - } - }); - } - - /** - * Handle the start event. - * - * @param event the event - * @throws IOException - * @throws ApiException - */ - @Handler(priority = 10) - public void onStart(Start event) { - try { - startWatching(); - } catch (IOException | ApiException e) { - logger.log(Level.SEVERE, e, - () -> "Cannot watch VMs, terminating."); - event.cancel(true); - fire(new Exit(1)); - } - } - - private void startWatching() throws IOException, ApiException { - // Get namespace - if (namespaceToWatch == null) { - var path = Path - .of("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); - if (Files.isReadable(path)) { - namespaceToWatch = Files.lines(path).findFirst().orElse(null); - } - } - // Availability already checked by Controller.onStart - logger.fine(() -> "Watching namespace \"" + namespaceToWatch + "\"."); - - // Get all our API versions - var client = Config.defaultClient(); - var apis = new ApisApi(client).getAPIVersions(); - var vmOpApiVersions = apis.getGroups().stream() - .filter(g -> g.getName().equals(VM_OP_GROUP)).findFirst() - .map(V1APIGroup::getVersions).stream().flatMap(l -> l.stream()) - .map(V1GroupVersionForDiscovery::getVersion).toList(); - - // Remove left overs - var coa = new CustomObjectsApi(client); - purge(client, coa, vmOpApiVersions); - - // Start a watcher thread for each existing CRD version. - // The watcher will send us an "ADDED" for each existing VM. - for (var version : vmOpApiVersions) { - coa.getAPIResources(VM_OP_GROUP, version) - .getResources().stream() - .filter(r -> VM_OP_KIND_VM.equals(r.getKind())) - .findFirst() - .ifPresent(crd -> watchVmDefs(crd, version)); - } - } - - @SuppressWarnings("PMD.CognitiveComplexity") - private void purge(ApiClient client, CustomObjectsApi coa, - List vmOpApiVersions) throws ApiException { - // Get existing CRs (VMs) - Set known = new HashSet<>(); - for (var version : vmOpApiVersions) { - // Get all known CR instances. - coa.getAPIResources(VM_OP_GROUP, version) - .getResources().stream() - .filter(r -> VM_OP_KIND_VM.equals(r.getKind())) - .findFirst() - .ifPresent(crd -> known.addAll(getKnown(client, crd, version))); - } - - ListOptions opts = new ListOptions(); - opts.setLabelSelector( - "app.kubernetes.io/managed-by=" + VM_OP_NAME + "," - + "app.kubernetes.io/name=" + APP_NAME); - for (String resource : List.of("apps/v1/statefulsets", - "v1/configmaps", "v1/secrets")) { - @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops", - "PMD.AvoidDuplicateLiterals" }) - 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(namespaceToWatch, 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(namespaceToWatch, resName); - if (!result.isSuccess()) { - logger.warning(() -> "Cannot cleanup resource \"" - + resName + "\": " + result.toString()); - } - } - } - } - } - - private Set getKnown(ApiClient client, V1APIResource crd, - String version) { - Set result = new HashSet<>(); - var api = new DynamicKubernetesApi(VM_OP_GROUP, version, - crd.getName(), client); - for (var item : api.list(namespaceToWatch).getObject().getItems()) { - if (!VM_OP_KIND_VM.equals(item.getKind())) { - continue; - } - result.add(item.getMetadata().getName()); - } - return result; - } - - private void watchVmDefs(V1APIResource crd, String version) { - @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops", - "PMD.AvoidCatchingThrowable", "PMD.AvoidCatchingGenericException" }) - var watcher = new Thread(() -> { - try { - logger.info(() -> "Watching objects created from " - + crd.getName() + "." + VM_OP_GROUP + "/" + version); - // Watch sometimes terminates without apparent reason. - while (true) { - Instant startedAt = Instant.now(); - var client = Config.defaultClient(); - var coa = new CustomObjectsApi(client); - var call = coa.listNamespacedCustomObjectCall(VM_OP_GROUP, - version, namespaceToWatch, crd.getName(), null, false, - null, null, null, null, null, null, null, true, null); - try (Watch watch - = Watch.createWatch(client, call, - new TypeToken>() { - }.getType())) { - for (Watch.Response item : watch) { - handleVmDefinitionChange(crd, item); - } - } catch (IOException | ApiException | RuntimeException e) { - logger.log(Level.FINE, e, () -> "Problem watching \"" - + crd.getName() + "\" (will retry): " - + e.getMessage()); - delayRestart(startedAt); - } - } - } catch (Throwable e) { - logger.log(Level.SEVERE, e, () -> "Probem watching: " - + e.getMessage()); - } - fire(new Stop()); - }); - watcher.setDaemon(true); - watcher.start(); - } - - @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") - private void delayRestart(Instant started) { - var runningFor = Duration - .between(started, Instant.now()).toMillis(); - if (runningFor < 5000) { - logger.log(Level.FINE, () -> "Waiting... "); - try { - Thread.sleep(5000 - runningFor); - } catch (InterruptedException e1) { // NOPMD - // Retry - } - logger.log(Level.FINE, () -> "Retrying"); - } - } - - private void handleVmDefinitionChange(V1APIResource vmsCrd, - Watch.Response vmDefRef) throws ApiException { - V1ObjectMeta metadata = vmDefRef.object.getMetadata(); - VmChannel channel = channels.computeIfAbsent(metadata.getName(), - k -> { - try { - return new VmChannel(channel(), newEventPipeline(), - new K8sClient()); - } catch (IOException e) { - logger.log(Level.SEVERE, e, () -> "Failed to create client" - + " for handling changes: " + e.getMessage()); - return null; - } - }); - if (channel == null) { - return; - } - - // Get full definition and associate with channel as backup - @SuppressWarnings("PMD.ShortVariable") - var gv = GroupVersion.parse(vmDefRef.object.getApiVersion()); - var vmStub = K8sDynamicStub.get(channel.client(), - new GroupVersionKind(gv.getGroup(), gv.getVersion(), VM_OP_KIND_VM), - metadata.getNamespace(), metadata.getName()); - vmStub.model().ifPresent(vmDef -> { - addDynamicData(channel.client(), vmDef); - channel.setVmDefinition(vmDef); - - // Create and fire event - channel.pipeline().fire(new VmDefChanged(VmDefChanged.Type - .valueOf(vmDefRef.type), - channel - .setGeneration( - vmDefRef.object.getMetadata().getGeneration()), - vmsCrd, vmDef), channel); - }); - } - - private void addDynamicData(K8sClient client, K8sDynamicModel vmState) { - var rootNode = GsonPtr.to(vmState.data()).get(JsonObject.class); - rootNode.addProperty("nodeName", ""); - - // VM definition status changes before the pod terminates. - // This results in pod information being shown for a stopped - // VM which is irritating. So check condition first. - var isRunning = GsonPtr.to(rootNode).to("status", "conditions") - .get(JsonArray.class) - .asList().stream().filter(el -> "Running" - .equals(((JsonObject) el).get("type").getAsString())) - .findFirst().map(el -> "True" - .equals(((JsonObject) el).get("status").getAsString())) - .orElse(false); - if (!isRunning) { - return; - } - var podSearch = new ListOptions(); - podSearch.setLabelSelector("app.kubernetes.io/name=" + APP_NAME - + ",app.kubernetes.io/component=" + APP_NAME - + ",app.kubernetes.io/instance=" + vmState.getMetadata().getName()); - try { - var podList - = K8sV1PodStub.list(client, namespaceToWatch, podSearch); - for (var podStub : podList) { - rootNode.addProperty("nodeName", - podStub.model().get().getSpec().getNodeName()); - } - } catch (ApiException e) { - logger.log(Level.WARNING, e, - () -> "Cannot access node information: " + e.getMessage()); - } - } - - /** - * Remove VM channel when VM is deleted. - * - * @param event the event - * @param channel the channel - */ - @Handler(priority = -10_000) - public void onVmDefChanged(VmDefChanged event, VmChannel channel) { - if (event.type() == Type.DELETED) { - channels.remove(event.vmDefinition().getMetadata().getName()); - } - } - -} diff --git a/org.jdrupes.vmoperator.runner.qemu/display-password b/org.jdrupes.vmoperator.runner.qemu/display-password new file mode 100644 index 0000000..97c1abb --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/display-password @@ -0,0 +1 @@ +test-vm \ No newline at end of file diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CdMediaController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CdMediaController.java index b7c960a..53922f2 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CdMediaController.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CdMediaController.java @@ -25,8 +25,8 @@ import java.util.concurrent.ConcurrentHashMap; import org.jdrupes.vmoperator.runner.qemu.commands.QmpChangeMedium; import org.jdrupes.vmoperator.runner.qemu.commands.QmpOpenTray; import org.jdrupes.vmoperator.runner.qemu.commands.QmpRemoveMedium; +import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; -import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; import org.jdrupes.vmoperator.runner.qemu.events.TrayMovedEvent; import org.jgrapes.core.Channel; @@ -68,7 +68,7 @@ public class CdMediaController extends Component { @Handler @SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition", "PMD.AvoidInstantiatingObjectsInLoops" }) - public void onConfigureQemu(RunnerConfigurationUpdate event) { + public void onConfigureQemu(ConfigureQemu event) { if (event.state() == State.TERMINATING) { return; } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java index 7fc3f95..b37d2c5 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java @@ -46,7 +46,7 @@ public class Configuration implements Dto { @SuppressWarnings("PMD.FieldNamingConventions") protected final Logger logger = Logger.getLogger(getClass().getName()); - /** Configuration timestamp */ + /** Configuration timestamp. */ public Instant asOf; /** The data dir. */ @@ -73,6 +73,9 @@ public class Configuration implements Dto { /** The firmware vars. */ public Path firmwareVars; + /** The display password. */ + public boolean hasDisplayPassword; + /** Optional cloud-init data. */ public CloudInit cloudInit; @@ -87,10 +90,16 @@ public class Configuration implements Dto { * Subsection "cloud-init". */ public static class CloudInit implements Dto { + + /** The meta data. */ @SuppressWarnings("PMD.UseConcurrentHashMap") public Map metaData; + + /** The user data. */ @SuppressWarnings("PMD.UseConcurrentHashMap") public Map userData; + + /** The network config. */ @SuppressWarnings("PMD.UseConcurrentHashMap") public Map networkConfig; } @@ -230,6 +239,8 @@ public class Configuration implements Dto { * The Class Display. */ public static class Display implements Dto { + + /** The spice. */ public Spice spice; } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java index f0face4..3071e42 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java @@ -27,11 +27,11 @@ import java.util.Set; import org.jdrupes.vmoperator.runner.qemu.commands.QmpAddCpu; import org.jdrupes.vmoperator.runner.qemu.commands.QmpDelCpu; import org.jdrupes.vmoperator.runner.qemu.commands.QmpQueryHotpluggableCpus; +import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; import org.jdrupes.vmoperator.runner.qemu.events.CpuAdded; import org.jdrupes.vmoperator.runner.qemu.events.CpuDeleted; import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; -import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; @@ -45,7 +45,7 @@ public class CpuController extends Component { private Integer currentCpus; private Integer desiredCpus; - private RunnerConfigurationUpdate suspendedConfigure; + private ConfigureQemu suspendedConfigure; /** * Instantiates a new CPU controller. @@ -62,7 +62,7 @@ public class CpuController extends Component { * @param event the event */ @Handler - public void onConfigureQemu(RunnerConfigurationUpdate event) { + public void onConfigureQemu(ConfigureQemu event) { if (event.state() == State.TERMINATING) { return; } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java new file mode 100644 index 0000000..882b85b --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java @@ -0,0 +1,117 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.runner.qemu; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; +import java.util.logging.Level; +import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetDisplayPassword; +import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; +import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; +import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; +import org.jgrapes.core.Channel; +import org.jgrapes.core.Component; +import org.jgrapes.core.annotation.Handler; +import org.jgrapes.util.events.FileChanged; +import org.jgrapes.util.events.WatchFile; + +/** + * The Class DisplayController. + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class DisplayController extends Component { + + public static final String DISPLAY_PASSWORD_FILE = "display-password"; + private String currentPassword; + private String protocol; + private final Path configDir; + + /** + * Instantiates a new Display controller. + * + * @param componentChannel the component channel + * @param configDir + */ + @SuppressWarnings("PMD.AssignmentToNonFinalStatic") + public DisplayController(Channel componentChannel, Path configDir) { + super(componentChannel); + this.configDir = configDir; + fire(new WatchFile(configDir.resolve(DISPLAY_PASSWORD_FILE))); + } + + /** + * On configure qemu. + * + * @param event the event + */ + @Handler + public void onConfigureQemu(ConfigureQemu event) { + if (event.state() == State.TERMINATING) { + return; + } + protocol + = event.configuration().vm.display.spice != null ? "spice" : null; + updatePassword(); + } + + /** + * Watch for changes of the password file. + * + * @param event the event + */ + @Handler + @SuppressWarnings("PMD.EmptyCatchBlock") + public void onFileChanged(FileChanged event) { + if (event.path().equals(configDir.resolve(DISPLAY_PASSWORD_FILE))) { + updatePassword(); + } + } + + @SuppressWarnings("PMD.DataflowAnomalyAnalysis") + private void updatePassword() { + if (protocol == null) { + return; + } + + String password; + Path dpPath = configDir.resolve(DISPLAY_PASSWORD_FILE); + if (dpPath.toFile().canRead()) { + logger.finer(() -> "Found display password"); + try { + password = Files.readString(dpPath); + } catch (IOException e) { + logger.log(Level.WARNING, e, () -> "Cannot read display" + + " password: " + e.getMessage()); + return; + } + } else { + logger.finer(() -> "No display password"); + return; + } + + if (Objects.equals(this.currentPassword, password)) { + return; + } + logger.fine(() -> "Updating display password"); + fire(new MonitorCommand(new QmpSetDisplayPassword(protocol, password))); + } + +} diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java index 3d22b26..84a99a4 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java @@ -35,12 +35,12 @@ import java.util.logging.Level; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCapabilities; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand; import org.jdrupes.vmoperator.runner.qemu.commands.QmpPowerdown; +import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; import org.jdrupes.vmoperator.runner.qemu.events.MonitorEvent; import org.jdrupes.vmoperator.runner.qemu.events.MonitorReady; import org.jdrupes.vmoperator.runner.qemu.events.MonitorResult; import org.jdrupes.vmoperator.runner.qemu.events.PowerdownEvent; -import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.Components; @@ -87,13 +87,16 @@ public class QemuMonitor extends Component { * Instantiates a new qemu monitor. * * @param componentChannel the component channel + * @param configDir the config dir * @throws IOException Signals that an I/O exception has occurred. */ @SuppressWarnings("PMD.AssignmentToNonFinalStatic") - public QemuMonitor(Channel componentChannel) throws IOException { + public QemuMonitor(Channel componentChannel, Path configDir) + throws IOException { super(componentChannel); attach(new RamController(channel())); attach(new CpuController(channel())); + attach(new DisplayController(channel(), configDir)); attach(new CdMediaController(channel())); } @@ -254,17 +257,18 @@ public class QemuMonitor extends Component { * @param event the event */ @Handler + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") public void onExecQmpCommand(MonitorCommand event) { var command = event.command(); + logger.fine(() -> "monitor(out): " + command.toString()); String asText; try { - asText = mapper.writeValueAsString(command.toJson()); + asText = command.asText(); } catch (JsonProcessingException e) { logger.log(Level.SEVERE, e, () -> "Cannot serialize Json: " + e.getMessage()); return; } - logger.fine(() -> "monitor(out): " + asText); synchronized (executing) { monitorChannel.associated(Writer.class).ifPresent(writer -> { try { @@ -343,7 +347,7 @@ public class QemuMonitor extends Component { * @param event the event */ @Handler - public void onConfigureQemu(RunnerConfigurationUpdate event) { + public void onConfigureQemu(ConfigureQemu event) { int newTimeout = event.configuration().vm.powerdownTimeout; if (powerdownTimeout != newTimeout) { powerdownTimeout = newTimeout; diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/RamController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/RamController.java index 05fdde6..9cdc2b5 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/RamController.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/RamController.java @@ -21,8 +21,8 @@ package org.jdrupes.vmoperator.runner.qemu; import java.math.BigInteger; import java.util.Optional; import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetBalloon; +import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; -import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.annotation.Handler; @@ -50,7 +50,7 @@ public class RamController extends Component { * @param event the event */ @Handler - public void onConfigureQemu(RunnerConfigurationUpdate event) { + public void onConfigureQemu(ConfigureQemu event) { Optional.ofNullable(event.configuration().vm.currentRam) .ifPresent(cr -> { if (currentRam != null && currentRam.equals(cr)) { diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index 922f2af..624d949 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -1,6 +1,6 @@ /* * VM-Operator - * Copyright (C) 2023 Michael N. Lipp + * Copyright (C) 2023,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 @@ -55,10 +55,10 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import static org.jdrupes.vmoperator.common.Constants.APP_NAME; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCont; +import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; import org.jdrupes.vmoperator.runner.qemu.events.Exit; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; import org.jdrupes.vmoperator.runner.qemu.events.QmpConfigured; -import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; import org.jdrupes.vmoperator.util.ExtendedObjectWrapper; @@ -143,8 +143,8 @@ import org.jgrapes.util.events.WatchFile; * waitForConfigured: entry/fire QmpCapabilities * waitForConfigured --> configure: QmpConfigured * - * configure: entry/fire RunnerConfigurationUpdate - * configure --> success: RunnerConfigurationUpdate (last handler)/fire cont command + * configure: entry/fire ConfigureQemu + * configure --> success: ConfigureQemu (last handler)/fire cont command * } * * Initializing --> prepFork: Started @@ -207,6 +207,7 @@ public class Runner extends Component { private final JsonNode defaults; @SuppressWarnings("PMD.UseConcurrentHashMap") private final File configFile; + private final Path configDir; private Configuration config = new Configuration(); private final freemarker.template.Configuration fmConfig; private CommandDefinition swtpmDefinition; @@ -240,6 +241,17 @@ public class Runner extends Component { defaults = yamlMapper.readValue( Runner.class.getResourceAsStream("defaults.yaml"), JsonNode.class); + // Get the config + configFile = new File(cmdLine.getOptionValue('c', + "/etc/opt/" + APP_NAME.replace("-", "") + "/config.yaml")); + // Don't rely on night config to produce a good exception + // for this simple case + if (!Files.isReadable(configFile.toPath())) { + throw new IOException( + "Cannot read configuration file " + configFile); + } + configDir = configFile.getParentFile().toPath().toRealPath(); + // Configure freemarker library fmConfig = new freemarker.template.Configuration( freemarker.template.Configuration.VERSION_2_3_32); @@ -256,17 +268,8 @@ public class Runner extends Component { attach(new FileSystemWatcher(channel())); attach(new ProcessManager(channel())); attach(new SocketConnector(channel())); - attach(qemuMonitor = new QemuMonitor(channel())); + attach(qemuMonitor = new QemuMonitor(channel(), configDir)); attach(new StatusUpdater(channel())); - - configFile = new File(cmdLine.getOptionValue('c', - "/etc/opt/" + APP_NAME.replace("-", "") + "/config.yaml")); - // Don't rely on night config to produce a good exception - // for this simple case - if (!Files.isReadable(configFile.toPath())) { - throw new IOException( - "Cannot read configuration file " + configFile); - } attach(new YamlConfigurationStore(channel(), configFile, false)); fire(new WatchFile(configFile.toPath())); } @@ -294,13 +297,20 @@ public class Runner extends Component { public void onConfigurationUpdate(ConfigurationUpdate event) { event.structured(componentPath()).ifPresent(c -> { var newConf = yamlMapper.convertValue(c, Configuration.class); + + // Add some values from other sources to configuration newConf.asOf = Instant.ofEpochSecond(configFile.lastModified()); + Path dsPath + = configDir.resolve(DisplayController.DISPLAY_PASSWORD_FILE); + newConf.hasDisplayPassword = dsPath.toFile().canRead(); + + // Special actions for initial configuration (startup) if (event instanceof InitialConfiguration) { processInitialConfiguration(newConf); return; } logger.fine(() -> "Updating configuration"); - rep.fire(new RunnerConfigurationUpdate(newConf, state)); + rep.fire(new ConfigureQemu(newConf, state)); }); } @@ -388,12 +398,9 @@ public class Runner extends Component { .map(Object::toString).orElse(null)); model.put("firmwareVars", Optional.ofNullable(config.firmwareVars) .map(Object::toString).orElse(null)); + model.put("hasDisplayPassword", config.hasDisplayPassword); model.put("cloudInit", config.cloudInit); model.put("vm", config.vm); - if (Optional.ofNullable(config.vm.display) - .map(d -> d.spice).map(s -> s.ticket).isPresent()) { - model.put("ticketPath", config.runtimeDir.resolve("ticket.txt")); - } // Combine template and data and parse result // (tempting, but no need to use a pipe here) @@ -598,7 +605,7 @@ public class Runner extends Component { */ @Handler public void onQmpConfigured(QmpConfigured event) { - rep.fire(new RunnerConfigurationUpdate(config, state)); + rep.fire(new ConfigureQemu(config, state)); } /** @@ -607,7 +614,7 @@ public class Runner extends Component { * @param event the event */ @Handler(priority = -1000) - public void onConfigureQemu(RunnerConfigurationUpdate event) { + public void onConfigureQemu(ConfigureQemu event) { if (state == State.STARTING) { fire(new MonitorCommand(new QmpCont())); state = State.RUNNING; diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index d55e027..d416fa1 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -42,9 +42,9 @@ import org.jdrupes.vmoperator.common.K8sClient; import org.jdrupes.vmoperator.common.K8sDynamicModel; import org.jdrupes.vmoperator.common.K8sDynamicStub; import org.jdrupes.vmoperator.runner.qemu.events.BalloonChangeEvent; +import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; import org.jdrupes.vmoperator.runner.qemu.events.Exit; import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus; -import org.jdrupes.vmoperator.runner.qemu.events.RunnerConfigurationUpdate; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; import org.jdrupes.vmoperator.runner.qemu.events.ShutdownEvent; @@ -178,7 +178,7 @@ public class StatusUpdater extends Component { * @throws ApiException */ @Handler - public void onRunnerConfigurationUpdate(RunnerConfigurationUpdate event) + public void onConfigureQemu(ConfigureQemu event) throws ApiException { guestShutdownStops = event.configuration().guestShutdownStops; diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpCommand.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpCommand.java index 8a03ab0..f91d702 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpCommand.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpCommand.java @@ -18,6 +18,7 @@ package org.jdrupes.vmoperator.runner.qemu.commands; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; @@ -55,4 +56,30 @@ public abstract class QmpCommand { * @return the json node */ public abstract JsonNode toJson(); + + /** + * Returns the string representation. + * + * @return the string + * @throws JsonProcessingException the JSON processing exception + */ + public String asText() throws JsonProcessingException { + return mapper.writeValueAsString(toJson()); + } + + /** + * Calls {@link #asText()} but suppresses the + * {@link JsonProcessingException}. + * + * @return the string + */ + @Override + public String toString() { + try { + return asText(); + } catch (JsonProcessingException e) { + return "(no string representation)"; + } + } + } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpSetDisplayPassword.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpSetDisplayPassword.java new file mode 100644 index 0000000..0048b9a --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpSetDisplayPassword.java @@ -0,0 +1,68 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.runner.qemu.commands; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; + +/** + * A {@link QmpCommand} that sets the display password. + */ +public class QmpSetDisplayPassword extends QmpCommand { + + private final String password; + private final String protocol; + + /** + * Instantiates a new command. + * + * @param protocol the protocol + * @param password the password + */ + public QmpSetDisplayPassword(String protocol, String password) { + this.protocol = protocol; + this.password = password; + } + + @Override + public JsonNode toJson() { + ObjectNode cmd = mapper.createObjectNode(); + cmd.put("execute", "set_password"); + ObjectNode args = mapper.createObjectNode(); + cmd.set("arguments", args); + args.set("protocol", new TextNode(protocol)); + args.set("password", new TextNode(password)); + return cmd; + } + + @Override + public String toString() { + try { + var json = toJson(); + ((ObjectNode) json.get("arguments")).set("password", + new TextNode("********")); + return mapper.writeValueAsString(json); + } catch (JsonProcessingException e) { + return "(no string representation)"; + } + } + +} diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerConfigurationUpdate.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ConfigureQemu.java similarity index 93% rename from org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerConfigurationUpdate.java rename to org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ConfigureQemu.java index bdb0c73..a8b1e45 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerConfigurationUpdate.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ConfigureQemu.java @@ -31,7 +31,7 @@ import org.jgrapes.core.Event; * on the event and only {@link Event#resumeHandling() resume handling} * when the adaption has completed. */ -public class RunnerConfigurationUpdate extends Event { +public class ConfigureQemu extends Event { private final Configuration configuration; private final State state; @@ -41,7 +41,7 @@ public class RunnerConfigurationUpdate extends Event { * * @param channels the channels */ - public RunnerConfigurationUpdate(Configuration configuration, State state, + public ConfigureQemu(Configuration configuration, State state, Channel... channels) { super(channels); this.state = state; diff --git a/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml b/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml index aa7f49e..d100554 100644 --- a/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml +++ b/org.jdrupes.vmoperator.runner.qemu/templates/Standard-VM-latest.ftl.yaml @@ -215,12 +215,8 @@ <#assign spice = vm.display.spice/> # SPICE (display, channels ...) # https://www.linux-kvm.org/page/SPICE - <#if ticketPath??> - - [ "-object", "secret,id=spiceTicket,file=${ ticketPath }" ] - - [ "-spice", "port=${ spice.port?c }\ - <#if spice.ticket??>,password-secret=spiceTicket\ - <#else>,disable-ticketing=on\ + ,disable-ticketing=<#if hasDisplayPassword!false>off<#else>on\ <#if spice.streamingVideo??>,streaming-video=${ spice.streamingVideo }\ ,seamless-migration=on" ] - [ "-chardev", "spicevmc,id=vdagentdev,name=vdagent" ] diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java index 993a844..025ce3c 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java @@ -31,17 +31,16 @@ import java.math.BigInteger; import java.time.Duration; import java.time.Instant; import java.util.HashSet; -import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import org.jdrupes.json.JsonBeanDecoder; import org.jdrupes.json.JsonDecodeException; import org.jdrupes.vmoperator.common.K8sDynamicModel; +import org.jdrupes.vmoperator.common.K8sObserver; +import org.jdrupes.vmoperator.manager.events.ChannelCache; import org.jdrupes.vmoperator.manager.events.ModifyVm; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; -import org.jdrupes.vmoperator.manager.events.VmDefChanged.Type; import org.jdrupes.vmoperator.util.GsonPtr; import org.jgrapes.core.Channel; import org.jgrapes.core.Event; @@ -69,10 +68,8 @@ public class VmConlet extends FreeMarkerConlet { private static final Set MODES = RenderMode.asSet( RenderMode.Preview, RenderMode.View); - private final Map vmInfos - = new ConcurrentHashMap<>(); - private final Map vmChannels - = new ConcurrentHashMap<>(); + private final ChannelCache channelManager = new ChannelCache<>(); private final TimeSeries summarySeries = new TimeSeries(Duration.ofDays(1)); private Summary cachedSummary; @@ -162,9 +159,10 @@ public class VmConlet extends FreeMarkerConlet { sendVmInfos = true; } if (sendVmInfos) { - for (var vmInfo : vmInfos.values()) { - var def = JsonBeanDecoder.create(vmInfo.data().toString()) - .readObject(); + for (var vmDef : channelManager.associated()) { + var def + = JsonBeanDecoder.create(vmDef.data().toString()) + .readObject(); channel.respond(new NotifyConletView(type(), conletId, "updateVm", def)); } @@ -187,9 +185,8 @@ public class VmConlet extends FreeMarkerConlet { public void onVmDefChanged(VmDefChanged event, VmChannel channel) throws JsonDecodeException, IOException { var vmName = event.vmDefinition().getMetadata().getName(); - if (event.type() == Type.DELETED) { - vmInfos.remove(vmName); - vmChannels.remove(vmName); + if (event.type() == K8sObserver.ResponseType.DELETED) { + channelManager.remove(vmName); for (var entry : conletIdsByConsoleConnection().entrySet()) { for (String conletId : entry.getValue()) { entry.getKey().respond(new NotifyConletView(type(), @@ -199,8 +196,7 @@ public class VmConlet extends FreeMarkerConlet { } else { var vmDef = new K8sDynamicModel(channel.client().getJSON() .getGson(), convertQuantities(event.vmDefinition().data())); - vmInfos.put(vmName, vmDef); - vmChannels.put(vmName, channel); + channelManager.put(vmName, channel, vmDef); var def = JsonBeanDecoder.create(vmDef.data().toString()) .readObject(); for (var entry : conletIdsByConsoleConnection().entrySet()) { @@ -323,7 +319,7 @@ public class VmConlet extends FreeMarkerConlet { return cachedSummary; } Summary summary = new Summary(); - for (var vmDef : vmInfos.values()) { + for (var vmDef : channelManager.associated()) { summary.totalVms += 1; var status = GsonPtr.to(vmDef.data()).to("status"); summary.usedCpus += status.getAsInt("cpus").orElse(0); @@ -349,7 +345,7 @@ public class VmConlet extends FreeMarkerConlet { throws Exception { event.stop(); var vmName = event.params().asString(0); - var vmChannel = vmChannels.get(vmName); + var vmChannel = channelManager.channel(vmName).orElse(null); if (vmChannel == null) { return; } From 690215d73c1c04b54f926227ff5a7836e9dc6288 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:40:41 +0100 Subject: [PATCH 129/462] Add permission (#22) --- deploy/vmop-role.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/deploy/vmop-role.yaml b/deploy/vmop-role.yaml index e1ea85b..0b0e94a 100644 --- a/deploy/vmop-role.yaml +++ b/deploy/vmop-role.yaml @@ -31,5 +31,6 @@ rules: - pods verbs: - list + - get - delete - patch From 9209ba0078462a3f5f066493890e8c3a0ead23e2 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Wed, 20 Mar 2024 17:59:28 +0100 Subject: [PATCH 130/462] Fix search for secret. (#23) --- .../vmoperator/common/K8sV1SecretStub.java | 31 +++++++++++++++++++ .../vmoperator/manager/Controller.java | 2 +- .../vmoperator/manager/Reconciler.java | 27 ++++++++++++++-- .../manager/StatefulSetReconciler.java | 8 ----- 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java index d2fcaf3..18973e0 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java @@ -19,9 +19,13 @@ package org.jdrupes.vmoperator.common; import io.kubernetes.client.Discovery.APIResource; +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.generic.options.ListOptions; +import java.util.Collection; import java.util.List; +import org.jdrupes.vmoperator.common.K8sGenericStub.GenericSupplier; /** * A stub for secrets (v1). @@ -57,4 +61,31 @@ public class K8sV1SecretStub extends K8sGenericStub { String name) { return new K8sV1SecretStub(client, namespace, name); } + + /** + * Get the stubs for the objects in the given namespace that match + * the criteria from the given options. + * + * @param client the client + * @param namespace the namespace + * @param options the options + * @return the collection + * @throws ApiException the api exception + */ + public static Collection list(K8sClient client, + String namespace, ListOptions options) throws ApiException { + return K8sGenericStub.list(V1Secret.class, V1SecretList.class, client, + CONTEXT, namespace, options, K8sV1SecretStub::getGeneric); + } + + /** + * Provide {@link GenericSupplier}. + */ + @SuppressWarnings("PMD.UnusedFormalParameter") + private static K8sV1SecretStub getGeneric(Class objectClass, + Class objectListClass, K8sClient client, + APIResource context, String namespace, String name) { + return new K8sV1SecretStub(client, namespace, name); + } + } \ No newline at end of file diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java index 4b8e5aa..cf47f9c 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java @@ -198,7 +198,7 @@ public class Controller extends Component { client.defaultPatchOptions()); if (!res.isPresent()) { logger.warning( - () -> "Cannot patch pod annotations for " + vmStub.name()); + () -> "Cannot patch definition for Vm " + vmStub.name()); } } } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index 07944cf..e0fdca0 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -34,6 +34,7 @@ import freemarker.template.TemplateNotFoundException; import io.kubernetes.client.custom.Quantity; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject; +import io.kubernetes.client.util.generic.options.ListOptions; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; @@ -43,9 +44,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import static org.jdrupes.vmoperator.common.Constants.APP_NAME; import org.jdrupes.vmoperator.common.Convertions; +import org.jdrupes.vmoperator.common.K8sClient; import org.jdrupes.vmoperator.common.K8sDynamicModel; import org.jdrupes.vmoperator.common.K8sObserver; +import org.jdrupes.vmoperator.common.K8sV1SecretStub; +import static org.jdrupes.vmoperator.manager.Constants.COMP_DISPLAY_SECRET; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.util.ExtendedObjectWrapper; @@ -200,7 +205,8 @@ public class Reconciler extends Component { } // Reconcile, use "augmented" vm definition for model - Map model = prepareModel(patchCr(event.vmDefinition())); + Map model + = prepareModel(channel.client(), patchCr(event.vmDefinition())); var configMap = cmReconciler.reconcile(event, model, channel); model.put("cm", configMap.getRaw()); stsReconciler.reconcile(event, model, channel); @@ -263,8 +269,9 @@ public class Reconciler extends Component { } @SuppressWarnings("PMD.CognitiveComplexity") - private Map prepareModel(DynamicKubernetesObject vmDef) - throws TemplateModelException { + private Map prepareModel(K8sClient client, + DynamicKubernetesObject vmDef) + throws TemplateModelException, ApiException { @SuppressWarnings("PMD.UseConcurrentHashMap") Map model = new HashMap<>(); model.put("managerVersion", @@ -278,6 +285,20 @@ public class Reconciler extends Component { .get(Constants.class.getName())); model.put("reconciler", config); + // Check if we have a display secret + ListOptions options = new ListOptions(); + options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + "," + + "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET + "," + + "app.kubernetes.io/instance=" + vmDef.getMetadata().getName()); + var dsStub = K8sV1SecretStub + .list(client, vmDef.getMetadata().getNamespace(), options).stream() + .findFirst(); + if (dsStub.isPresent()) { + dsStub.get().model().ifPresent(m -> { + model.put("displaySecret", m.getMetadata().getName()); + }); + } + // Methods model.put("parseQuantity", new TemplateMethodModelEx() { @Override diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java index e642502..8812a93 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java @@ -28,7 +28,6 @@ import java.io.IOException; import java.io.StringWriter; import java.util.Map; import java.util.logging.Logger; -import org.jdrupes.vmoperator.common.K8sV1SecretStub; import org.jdrupes.vmoperator.common.K8sV1StatefulSetStub; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; @@ -70,13 +69,6 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; throws IOException, TemplateException, ApiException { var metadata = event.vmDefinition().getMetadata(); - // Check if we have a display secret - var dsStub = K8sV1SecretStub.get(channel.client(), - metadata.getNamespace(), metadata.getName() + "-display-secret"); - dsStub.model().ifPresent(m -> { - model.put("displaySecret", m.getMetadata().getName()); - }); - // Combine template and data and parse result var fmTemplate = fmConfig.getTemplate("runnerSts.ftl.yaml"); StringWriter out = new StringWriter(); From 383d1c5cca134051690d12ff4a7a9746f2df5a41 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 28 Mar 2024 16:46:58 +0100 Subject: [PATCH 131/462] Reflect display password changes in status. --- deploy/crds/vms-crd.yaml | 5 +++ .../vmoperator/runner/qemu/StatusUpdater.java | 21 ++++++++++ .../qemu/events/DisplayPasswordChanged.java | 39 +++++++++++++++++++ .../runner/qemu/events/MonitorResult.java | 4 ++ 4 files changed, 69 insertions(+) create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/DisplayPasswordChanged.java diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index 1863afe..e4db3d5 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -1411,6 +1411,11 @@ spec: Amount of memory in use. type: string default: "0" + displayPasswordSerial: + description: >- + Counts changes of the display password. + type: integer + default: 0 conditions: description: >- List of component conditions observed diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index d416fa1..bb15639 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -43,6 +43,7 @@ import org.jdrupes.vmoperator.common.K8sDynamicModel; import org.jdrupes.vmoperator.common.K8sDynamicStub; import org.jdrupes.vmoperator.runner.qemu.events.BalloonChangeEvent; import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; +import org.jdrupes.vmoperator.runner.qemu.events.DisplayPasswordChanged; import org.jdrupes.vmoperator.runner.qemu.events.Exit; import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; @@ -321,6 +322,26 @@ public class StatusUpdater extends Component { }); } + /** + * On ballon change. + * + * @param event the event + * @throws ApiException + */ + @Handler + public void onDisplayPasswordChanged(DisplayPasswordChanged event) + throws ApiException { + if (vmStub == null) { + return; + } + vmStub.updateStatus(from -> { + JsonObject status = from.status(); + status.addProperty("displayPasswordSerial", + status.get("displayPasswordSerial").getAsLong() + 1); + return status; + }); + } + /** * On shutdown. * diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/DisplayPasswordChanged.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/DisplayPasswordChanged.java new file mode 100644 index 0000000..0814f50 --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/DisplayPasswordChanged.java @@ -0,0 +1,39 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.runner.qemu.events; + +import com.fasterxml.jackson.databind.JsonNode; +import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand; + +/** + * A {@link MonitorResult} that indicates that the display password has changed. + */ +public class DisplayPasswordChanged extends MonitorResult { + + /** + * Instantiates a new display password changed. + * + * @param command the command + * @param response the response + */ + public DisplayPasswordChanged(QmpCommand command, JsonNode response) { + super(command, response); + } + +} diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java index c0f55fe..9352ab2 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java @@ -25,6 +25,7 @@ import org.jdrupes.vmoperator.runner.qemu.commands.QmpCapabilities; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand; import org.jdrupes.vmoperator.runner.qemu.commands.QmpDelCpu; import org.jdrupes.vmoperator.runner.qemu.commands.QmpQueryHotpluggableCpus; +import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetDisplayPassword; import org.jgrapes.core.Channel; import org.jgrapes.core.Components; import org.jgrapes.core.Event; @@ -57,6 +58,9 @@ public class MonitorResult extends Event { if (command instanceof QmpDelCpu) { return new CpuDeleted(command, response); } + if (command instanceof QmpSetDisplayPassword) { + return new DisplayPasswordChanged(command, response); + } return new MonitorResult(command, response); } From 98f5c1e40229c9a8b4edd37f33a862bb15d1e99b Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Wed, 10 Apr 2024 11:14:32 +0200 Subject: [PATCH 132/462] Experimental spice proxy provider. --- settings.gradle | 1 + spice-squid/.checkstyle | 10 +++ spice-squid/.eclipse-pmd | 7 ++ spice-squid/.settings/net.sf.jautodoc.prefs | 8 ++ .../org.eclipse.buildship.core.prefs | 13 ++++ .../org.eclipse.core.resources.prefs | 2 + .../.settings/org.eclipse.core.runtime.prefs | 2 + spice-squid/Containerfile | 11 +++ spice-squid/build.gradle | 77 +++++++++++++++++++ spice-squid/run.sh | 19 +++++ spice-squid/squid.conf | 4 + 11 files changed, 154 insertions(+) create mode 100644 spice-squid/.checkstyle create mode 100644 spice-squid/.eclipse-pmd create mode 100644 spice-squid/.settings/net.sf.jautodoc.prefs create mode 100644 spice-squid/.settings/org.eclipse.buildship.core.prefs create mode 100644 spice-squid/.settings/org.eclipse.core.resources.prefs create mode 100644 spice-squid/.settings/org.eclipse.core.runtime.prefs create mode 100644 spice-squid/Containerfile create mode 100644 spice-squid/build.gradle create mode 100755 spice-squid/run.sh create mode 100644 spice-squid/squid.conf diff --git a/settings.gradle b/settings.gradle index cb613b6..cf075af 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,3 +16,4 @@ include 'org.jdrupes.vmoperator.vmconlet' include 'org.jdrupes.vmoperator.runner.qemu' include 'org.jdrupes.vmoperator.common' include 'org.jdrupes.vmoperator.util' +include 'spice-squid' diff --git a/spice-squid/.checkstyle b/spice-squid/.checkstyle new file mode 100644 index 0000000..7f2c604 --- /dev/null +++ b/spice-squid/.checkstyle @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/spice-squid/.eclipse-pmd b/spice-squid/.eclipse-pmd new file mode 100644 index 0000000..8b394f8 --- /dev/null +++ b/spice-squid/.eclipse-pmd @@ -0,0 +1,7 @@ + + + + + + + diff --git a/spice-squid/.settings/net.sf.jautodoc.prefs b/spice-squid/.settings/net.sf.jautodoc.prefs new file mode 100644 index 0000000..03e8200 --- /dev/null +++ b/spice-squid/.settings/net.sf.jautodoc.prefs @@ -0,0 +1,8 @@ +add_header=true +eclipse.preferences.version=1 +header_text=/*\n * VM-Operator\n * Copyright (C) 2024 Michael N. Lipp\n * \n * This program is free software\: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see .\n */ +project_specific_settings=true +replacements=\n\n\nReturns the\nSets the\nAdds the\nEdits the\nRemoves the\nInits the\nParses the\nCreates the\nBuilds the\nChecks if is\nPrints the\nChecks for\n\n\n +visibility_package=false +visibility_private=false +visibility_protected=false diff --git a/spice-squid/.settings/org.eclipse.buildship.core.prefs b/spice-squid/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..258eb47 --- /dev/null +++ b/spice-squid/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) +connection.project.dir=.. +eclipse.preferences.version=1 +gradle.user.home= +java.home= +jvm.arguments= +offline.mode=false +override.workspace.settings=false +show.console.view=false +show.executions.view=false diff --git a/spice-squid/.settings/org.eclipse.core.resources.prefs b/spice-squid/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/spice-squid/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/spice-squid/.settings/org.eclipse.core.runtime.prefs b/spice-squid/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 0000000..5a0ad22 --- /dev/null +++ b/spice-squid/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +line.separator=\n diff --git a/spice-squid/Containerfile b/spice-squid/Containerfile new file mode 100644 index 0000000..b65b631 --- /dev/null +++ b/spice-squid/Containerfile @@ -0,0 +1,11 @@ +FROM alpine:3.19 + +RUN apk update &&\ + apk add --no-cache inotify-tools &&\ + apk add --no-cache squid + +COPY run.sh /usr/local/bin/run-squid.sh + +CMD ["/usr/local/bin/run-squid.sh"] + +EXPOSE 3128 diff --git a/spice-squid/build.gradle b/spice-squid/build.gradle new file mode 100644 index 0000000..2cb7183 --- /dev/null +++ b/spice-squid/build.gradle @@ -0,0 +1,77 @@ +plugins { + id 'org.jdrupes.vmoperator.java-application-conventions' +} + +dependencies { +} + +task buildImage(type: Exec) { + inputs.files 'Containerfile' + + commandLine 'podman', 'build', '--pull', + '-t', "${project.name}:${project.version}",\ + '-f', 'Containerfile', '.' +} + +task tagLatestImage(type: Exec) { + dependsOn buildImage + + enabled = !project.version.contains("SNAPSHOT") + && !project.version.contains("alpha") \ + && !project.version.contains("beta") \ + || project.rootProject.properties['docker.testRegistry'] \ + && project.rootProject.properties['docker.registry'] \ + == project.rootProject.properties['docker.testRegistry'] + + commandLine 'podman', 'tag', "${project.name}:${project.version}",\ + "${project.name}:latest" +} + +task buildLatestImage { + dependsOn buildImage + dependsOn tagLatestImage +} + +task pushImage(type: Exec) { + dependsOn buildImage + + commandLine 'podman', 'push', '--tls-verify=false', \ + "localhost/${project.name}:${project.version}", \ + "${project.rootProject.properties['docker.registry']}" \ + + "/${project.name}:${project.version}" +} + +task pushLatestImage(type: Exec) { + dependsOn buildLatestImage + + enabled = !project.version.contains("SNAPSHOT") + && !project.version.contains("alpha") \ + && !project.version.contains("beta") \ + || project.rootProject.properties['docker.testRegistry'] \ + && project.rootProject.properties['docker.registry'] \ + == project.rootProject.properties['docker.testRegistry'] + + commandLine 'podman', 'push', '--tls-verify=false', \ + "localhost/${project.name}:${project.version}", \ + "${project.rootProject.properties['docker.registry']}" \ + + "/${project.name}:latest" +} + +task pushImages { + // Don't push without testing first + dependsOn pushImage + dependsOn pushLatestImage +} + +test { + enabled = project.hasProperty("k8s.testCluster") + + useJUnitPlatform() + + testLogging { + showStandardStreams = true + } + + systemProperty "k8s.testCluster", project.hasProperty("k8s.testCluster") + ? project.getProperty("k8s.testCluster") : null +} diff --git a/spice-squid/run.sh b/spice-squid/run.sh new file mode 100755 index 0000000..eddea39 --- /dev/null +++ b/spice-squid/run.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +CONF_OPT="-f /run/etc/squid/squid.conf" +/usr/sbin/squid $CONF_OPT + +inotifywait -m -e create -r /run/etc/squid | + while read file_path file_event file_name; do + if [ "$file_event" != "CREATE" ]; then + continue + fi + if [ -r /run/squid/squid.pid ]; then + echo "Reconfiguring squid" + /usr/sbin/squid $CONF_OPT -k reconfigure + else + echo "Restarting squid" + /usr/sbin/squid $CONF_OPT + fi + echo "Processed event" + done diff --git a/spice-squid/squid.conf b/spice-squid/squid.conf new file mode 100644 index 0000000..724b0df --- /dev/null +++ b/spice-squid/squid.conf @@ -0,0 +1,4 @@ +http_access deny all + +# Squid normally listens to port 3128 +http_port 3128 From b58c813e897249737265540ce2e0f39c62a34f50 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Wed, 17 Apr 2024 11:20:11 +0200 Subject: [PATCH 133/462] Make class thread safe. --- .../vmoperator/vmconlet/TimeSeries.java | 54 +++++++++---------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java index c7f634b..ee1667c 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/TimeSeries.java @@ -20,6 +20,7 @@ package org.jdrupes.vmoperator.vmconlet; import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -30,7 +31,8 @@ import java.util.List; @SuppressWarnings("PMD.DataflowAnomalyAnalysis") public class TimeSeries { - private final List data = new LinkedList<>(); + @SuppressWarnings("PMD.LooseCoupling") + private final LinkedList data = new LinkedList<>(); private final Duration period; /** @@ -52,25 +54,26 @@ public class TimeSeries { @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") public TimeSeries add(Instant time, Number... numbers) { var newEntry = new Entry(time, numbers); - boolean adjust = false; - if (data.size() >= 2) { - var lastEntry = data.get(data.size() - 1); - var lastButOneEntry = data.get(data.size() - 2); - adjust = lastEntry.valuesEqual(lastButOneEntry) - && lastEntry.valuesEqual(newEntry); - } - if (adjust) { - data.get(data.size() - 1).adjustTime(time); - } else { + boolean nothingNew = false; + synchronized (data) { + if (data.size() >= 2) { + var lastEntry = data.get(data.size() - 1); + var lastButOneEntry = data.get(data.size() - 2); + nothingNew = lastEntry.valuesEqual(lastButOneEntry) + && lastEntry.valuesEqual(newEntry); + } + if (nothingNew) { + data.removeLast(); + } data.add(new Entry(time, numbers)); - } - // Purge - Instant limit = time.minus(period); - while (data.size() > 2 - && data.get(0).getTime().isBefore(limit) - && data.get(1).getTime().isBefore(limit)) { - data.remove(0); + // Purge + Instant limit = time.minus(period); + while (data.size() > 2 + && data.get(0).getTime().isBefore(limit) + && data.get(1).getTime().isBefore(limit)) { + data.removeFirst(); + } } return this; } @@ -81,14 +84,16 @@ public class TimeSeries { * @return the list */ public List entries() { - return data; + synchronized (data) { + return new ArrayList<>(data); + } } /** * The Class Entry. */ public static class Entry { - private Instant timestamp; + private final Instant timestamp; private final Number[] values; /** @@ -103,15 +108,6 @@ public class TimeSeries { values = numbers; } - /** - * Changes the entry's time. - * - * @param time the time - */ - public void adjustTime(Instant time) { - timestamp = time; - } - /** * Returns the entry's time. * From 8b6acceda8bbc511b4b871afa81264280eef2998 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 13 May 2024 22:33:05 +0200 Subject: [PATCH 134/462] Re-enable PMD. --- org.jdrupes.vmoperator.common/.eclipse-pmd | 2 +- .../.eclipse-pmd | 2 +- org.jdrupes.vmoperator.manager/.eclipse-pmd | 2 +- .../.eclipse-pmd | 2 +- org.jdrupes.vmoperator.util/.eclipse-pmd | 2 +- org.jdrupes.vmoperator.vmconlet/.eclipse-pmd | 2 +- ruleset.xml | 20 ++++++++++--------- spice-squid/.eclipse-pmd | 2 +- 8 files changed, 18 insertions(+), 16 deletions(-) diff --git a/org.jdrupes.vmoperator.common/.eclipse-pmd b/org.jdrupes.vmoperator.common/.eclipse-pmd index 8b394f8..5d69caa 100644 --- a/org.jdrupes.vmoperator.common/.eclipse-pmd +++ b/org.jdrupes.vmoperator.common/.eclipse-pmd @@ -2,6 +2,6 @@ - + diff --git a/org.jdrupes.vmoperator.manager.events/.eclipse-pmd b/org.jdrupes.vmoperator.manager.events/.eclipse-pmd index 8b394f8..5d69caa 100644 --- a/org.jdrupes.vmoperator.manager.events/.eclipse-pmd +++ b/org.jdrupes.vmoperator.manager.events/.eclipse-pmd @@ -2,6 +2,6 @@ - + diff --git a/org.jdrupes.vmoperator.manager/.eclipse-pmd b/org.jdrupes.vmoperator.manager/.eclipse-pmd index 8b394f8..5d69caa 100644 --- a/org.jdrupes.vmoperator.manager/.eclipse-pmd +++ b/org.jdrupes.vmoperator.manager/.eclipse-pmd @@ -2,6 +2,6 @@ - + diff --git a/org.jdrupes.vmoperator.runner.qemu/.eclipse-pmd b/org.jdrupes.vmoperator.runner.qemu/.eclipse-pmd index 8b394f8..5d69caa 100644 --- a/org.jdrupes.vmoperator.runner.qemu/.eclipse-pmd +++ b/org.jdrupes.vmoperator.runner.qemu/.eclipse-pmd @@ -2,6 +2,6 @@ - + diff --git a/org.jdrupes.vmoperator.util/.eclipse-pmd b/org.jdrupes.vmoperator.util/.eclipse-pmd index 8b394f8..5d69caa 100644 --- a/org.jdrupes.vmoperator.util/.eclipse-pmd +++ b/org.jdrupes.vmoperator.util/.eclipse-pmd @@ -2,6 +2,6 @@ - + diff --git a/org.jdrupes.vmoperator.vmconlet/.eclipse-pmd b/org.jdrupes.vmoperator.vmconlet/.eclipse-pmd index 8b394f8..5d69caa 100644 --- a/org.jdrupes.vmoperator.vmconlet/.eclipse-pmd +++ b/org.jdrupes.vmoperator.vmconlet/.eclipse-pmd @@ -2,6 +2,6 @@ - + diff --git a/ruleset.xml b/ruleset.xml index b362eb8..365c409 100644 --- a/ruleset.xml +++ b/ruleset.xml @@ -1,9 +1,9 @@ + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> JGrapes rules @@ -27,14 +27,16 @@ JGrapes rules + - + + @@ -45,6 +47,11 @@ JGrapes rules value="Avoid variables with short names like id"/> + + + + + @@ -90,11 +97,6 @@ JGrapes rules - - - - - diff --git a/spice-squid/.eclipse-pmd b/spice-squid/.eclipse-pmd index 8b394f8..5d69caa 100644 --- a/spice-squid/.eclipse-pmd +++ b/spice-squid/.eclipse-pmd @@ -2,6 +2,6 @@ - + From 81ce3e244fb8235891256e0457d6c3bf7fb8350e Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 14 May 2024 11:27:09 +0200 Subject: [PATCH 135/462] Use OIDC login conlet. --- dev-example/config.yaml | 4 +-- org.jdrupes.vmoperator.manager/build.gradle | 7 ++-- .../jdrupes/vmoperator/manager/Manager.java | 35 +++++++++++++++---- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/dev-example/config.yaml b/dev-example/config.yaml index cf43692..8f6e64f 100644 --- a/dev-example/config.yaml +++ b/dev-example/config.yaml @@ -17,10 +17,10 @@ "/WebConsole": "/LoginConlet": users: - admin: + - name: admin fullName: Administrator password: "$2b$05$NiBd74ZGdplLC63ePZf1f.UtjMKkbQ23cQoO2OKOFalDBHWAOy21." - test: + - name: test fullName: Test Account password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2" "/RoleConfigurator": diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index a4990d5..f3e939b 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -16,14 +16,13 @@ dependencies { implementation 'org.jgrapes:org.jgrapes.core:[1.19.0,2)' implementation 'org.jgrapes:org.jgrapes.io:[2.7.0,3)' implementation 'org.jgrapes:org.jgrapes.http:[3.1.0,4)' - implementation 'org.jgrapes:org.jgrapes.util:[1.31.0,2)' + implementation 'org.jgrapes:org.jgrapes.util:[1.34.0,2)' - implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.3.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.4.0,2)' implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.5.0,2)' implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.0.0,2)' - implementation 'org.jgrapes:org.jgrapes.webconlet.locallogin:[1.0.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconlet.oidclogin:[1.0.0,2)' - runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.locallogin:[0.1.0,2)' runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.sysinfo:[1.4.0,2)' runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.logviewer:[0.2.0,2)' diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java index 8c8e0fd..9d291cf 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Manager.java @@ -50,19 +50,23 @@ import org.jgrapes.core.NamedChannel; import org.jgrapes.core.annotation.Handler; import org.jgrapes.core.events.HandlingError; import org.jgrapes.core.events.Stop; +import org.jgrapes.http.HttpConnector; import org.jgrapes.http.HttpServer; import org.jgrapes.http.InMemorySessionManager; import org.jgrapes.http.LanguageSelector; import org.jgrapes.http.events.Request; import org.jgrapes.io.NioDispatcher; import org.jgrapes.io.util.PermitsPool; +import org.jgrapes.net.SocketConnector; import org.jgrapes.net.SocketServer; +import org.jgrapes.net.SslCodec; import org.jgrapes.util.ComponentCollector; import org.jgrapes.util.FileSystemWatcher; import org.jgrapes.util.YamlConfigurationStore; import org.jgrapes.util.events.ConfigurationUpdate; import org.jgrapes.util.events.WatchFile; -import org.jgrapes.webconlet.locallogin.LoginConlet; +import org.jgrapes.webconlet.oidclogin.LoginConlet; +import org.jgrapes.webconlet.oidclogin.OidcClient; import org.jgrapes.webconsole.base.BrowserLocalBackedKVStore; import org.jgrapes.webconsole.base.ConletComponentFactory; import org.jgrapes.webconsole.base.ConsoleWeblet; @@ -71,6 +75,7 @@ import org.jgrapes.webconsole.base.PageResourceProviderFactory; import org.jgrapes.webconsole.base.WebConsole; import org.jgrapes.webconsole.rbac.RoleConfigurator; import org.jgrapes.webconsole.rbac.RoleConletFilter; +import org.jgrapes.webconsole.rbac.UserLogger; import org.jgrapes.webconsole.vuejs.VueJsConsoleWeblet; /** @@ -90,10 +95,11 @@ public class Manager extends Component { * @param cmdLine * * @throws IOException Signals that an I/O exception has occurred. + * @throws URISyntaxException */ @SuppressWarnings({ "PMD.TooFewBranchesForASwitchStatement", - "PMD.NcssCount" }) - public Manager(CommandLine cmdLine) throws IOException { + "PMD.NcssCount", "PMD.ConstructorCallsOverridableMethod" }) + public Manager(CommandLine cmdLine) throws IOException, URISyntaxException { super(new NamedChannel("manager")); // Prepare component tree attach(new NioDispatcher()); @@ -120,9 +126,19 @@ public class Manager extends Component { .setServerAddress(new InetSocketAddress(8080)) .setName("GuiSocketServer")); + // Channel for HTTP application layer + Channel httpChannel = new NamedChannel("guiHttp"); + + // Create network channels for client requests. + Channel requestChannel = attach(new SocketConnector(SELF)); + Channel secReqChannel + = attach(new SslCodec(SELF, requestChannel, true)); + // Support for making HTTP requests + attach(new HttpConnector(httpChannel, requestChannel, + secReqChannel)); + // Create an HTTP server as converter between transport and application // layer. - Channel httpChannel = new NamedChannel("guiHttp"); HttpServer guiHttpServer = attach(new HttpServer(httpChannel, httpTransport, Request.In.Get.class, Request.In.Post.class)); guiHttpServer.setName("GuiHttpServer"); @@ -138,7 +154,7 @@ public class Manager extends Component { return; } ConsoleWeblet consoleWeblet = guiHttpServer - .attach(new VueJsConsoleWeblet(httpChannel, Channel.SELF, rootUri) { + .attach(new VueJsConsoleWeblet(httpChannel, SELF, rootUri) { @Override protected Map createConsoleBaseModel() { return augmentBaseModel(super.createConsoleBaseModel()); @@ -156,9 +172,14 @@ public class Manager extends Component { console.attach(new RoleConfigurator(console.channel())); console.attach(new RoleConletFilter(console.channel())); console.attach(new LoginConlet(console.channel())); + console.attach(new OidcClient(console.channel(), httpChannel, + httpChannel, new URI("/oauth/callback"), 1500)); + console.attach(new UserLogger(console.channel())); + // Add all available page resource providers console.attach(new ComponentCollector<>( PageResourceProviderFactory.class, console.channel())); + // Add all available conlets console.attach(new ComponentCollector<>( ConletComponentFactory.class, console.channel(), type -> { @@ -299,7 +320,7 @@ public class Manager extends Component { try { app.fire(new Stop()); Components.awaitExhaustion(); - } catch (InterruptedException e) { + } catch (InterruptedException e) { // NOPMD // Cannot do anything about this. } })); @@ -310,7 +331,7 @@ public class Manager extends Component { // Wait for (regular) termination Components.awaitExhaustion(); System.exit(exitStatus); - } catch (IOException | InterruptedException + } catch (IOException | InterruptedException | URISyntaxException | org.apache.commons.cli.ParseException e) { Logger.getLogger(Manager.class.getName()).log(Level.SEVERE, e, () -> "Failed to start manager: " + e.getMessage()); From 6ffe287def354baab285b0a8fb645c0cc75ca1b2 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 18 May 2024 12:10:37 +0200 Subject: [PATCH 136/462] Upgrade base library. --- org.jdrupes.vmoperator.manager/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index f3e939b..bef587c 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -18,10 +18,10 @@ dependencies { implementation 'org.jgrapes:org.jgrapes.http:[3.1.0,4)' implementation 'org.jgrapes:org.jgrapes.util:[1.34.0,2)' - implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.4.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.5.0,2)' implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.5.0,2)' - implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.0.0,2)' - implementation 'org.jgrapes:org.jgrapes.webconlet.oidclogin:[1.0.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.2.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconlet.oidclogin:[1.3.0,2)' runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.sysinfo:[1.4.0,2)' runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.logviewer:[0.2.0,2)' From dac16da30b5daf6b07e15075dec322655301fee4 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 21 May 2024 09:03:07 +0200 Subject: [PATCH 137/462] Adapt to usage of oidc login conlet. --- dev-example/config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev-example/config.yaml b/dev-example/config.yaml index 8f6e64f..09183b4 100644 --- a/dev-example/config.yaml +++ b/dev-example/config.yaml @@ -39,4 +39,5 @@ - "*" # Others cannot use any conlet (except login conlet to log out) other: - - org.jgrapes.webconlet.locallogin.LoginConlet + - --org.jdrupes.vmoperator.vmconlet.VmConlet + - org.jgrapes.webconlet.oidclogin.LoginConlet From b6f0299932bdf7193421ed6b28ceee3593d6bf3c Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 21 May 2024 09:03:20 +0200 Subject: [PATCH 138/462] Upgrade base library. --- org.jdrupes.vmoperator.manager/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index bef587c..3018d0a 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -20,7 +20,7 @@ dependencies { implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.5.0,2)' implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.5.0,2)' - implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.2.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.3.0,2)' implementation 'org.jgrapes:org.jgrapes.webconlet.oidclogin:[1.3.0,2)' runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.sysinfo:[1.4.0,2)' From a6525a2289069d182c50371e32913fa65d52d36e Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Mon, 27 May 2024 12:57:01 +0200 Subject: [PATCH 139/462] Add viewer conlet (#25) --- .settings/org.eclipse.buildship.core.prefs | 2 +- deploy/crds/vms-crd.yaml | 16 +- dev-example/config.yaml | 4 + dev-example/kustomization.yaml | 5 +- dev-example/test-vm-display-secret.yaml | 1 + .../jdrupes/vmoperator/common/Constants.java | 1 + .../org/jdrupes/vmoperator/common/K8s.java | 3 +- .../jdrupes/vmoperator/common/K8sClient.java | 523 ++++++++++++------ .../common/K8sClusterGenericStub.java | 401 ++++++++++++++ .../K8sDynamicModelTypeAdapterFactory.java | 5 +- .../vmoperator/common/K8sObserver.java | 2 +- .../vmoperator/common/K8sV1NodeStub.java | 85 +++ .../vmoperator/common/K8sV1PodStub.java | 3 +- .../vmoperator/common/K8sV1SecretStub.java | 3 +- .../vmoperator/common/K8sV1ServiceStub.java | 92 +++ .../build.gradle | 1 + .../manager/events/ChannelCache.java | 2 +- ...anged.java => DisplayPasswordChanged.java} | 7 +- .../manager/events/GetDisplayPassword.java | 58 ++ .../manager/events/ServiceChanged.java | 76 +++ .../manager/events/VmDefChanged.java | 3 +- org.jdrupes.vmoperator.manager/build.gradle | 1 + .../vmoperator/manager/runnerConfig.ftl.yaml | 2 +- .../manager/ConfigMapReconciler.java | 2 +- .../jdrupes/vmoperator/manager/Constants.java | 1 + .../vmoperator/manager/Controller.java | 6 +- .../manager/DisplayPasswordMonitor.java | 102 ++++ .../vmoperator/manager/Reconciler.java | 1 + ...ecretsMonitor.java => ServiceMonitor.java} | 29 +- .../manager/StatefulSetReconciler.java | 7 +- .../jdrupes/vmoperator/manager/VmMonitor.java | 14 +- .../password-expiry | 1 + .../vmoperator/runner/qemu/Configuration.java | 5 +- .../vmoperator/runner/qemu/CpuController.java | 3 +- .../runner/qemu/DisplayController.java | 35 +- .../vmoperator/runner/qemu/QemuMonitor.java | 6 +- .../vmoperator/runner/qemu/Runner.java | 44 +- .../vmoperator/runner/qemu/StatusUpdater.java | 18 +- .../runner/qemu/commands/QmpAddCpu.java | 2 +- .../qemu/commands/QmpSetPasswordExpiry.java | 66 +++ .../qemu/events/HotpluggableCpuStatus.java | 2 + .../runner/qemu/events/MonitorCommand.java | 3 +- .../runner/qemu/events/MonitorResult.java | 3 +- .../runner/qemu/events/RunnerStateChange.java | 5 +- .../vmoperator/vmconlet/l10n.properties | 2 +- .../vmoperator/vmconlet/l10n_de.properties | 2 +- .../jdrupes/vmoperator/vmconlet/VmConlet.java | 6 +- .../vmconlet/browser/VmConlet-style.scss | 8 +- org.jdrupes.vmoperator.vmviewer/.checkstyle | 10 + org.jdrupes.vmoperator.vmviewer/.eclipse-pmd | 7 + org.jdrupes.vmoperator.vmviewer/.eslintignore | 1 + .../.eslintrc.json | 15 + org.jdrupes.vmoperator.vmviewer/.gitignore | 4 + .../org.eclipse.buildship.core.prefs | 10 + .../org.eclipse.core.resources.prefs | 2 + .../.settings/org.eclipse.core.runtime.prefs | 2 + .../.settings/org.eclipse.jdt.ui.prefs | 63 +++ org.jdrupes.vmoperator.vmviewer/build.gradle | 57 ++ org.jdrupes.vmoperator.vmviewer/package.json | 1 + ...pes.webconsole.base.ConletComponentFactory | 1 + .../vmviewer/VmViewer-edit.ftl.html | 20 + .../vmviewer/VmViewer-l10nBundles.ftl.js | 31 ++ .../vmviewer/VmViewer-preview.ftl.html | 6 + .../vmoperator/vmviewer/computer-off.svg | 74 +++ .../jdrupes/vmoperator/vmviewer/computer.svg | 74 +++ .../vmoperator/vmviewer/l10n.properties | 3 + .../vmoperator/vmviewer/l10n_de.properties | 7 + .../vmoperator/vmviewer/l10n_en.properties | 0 .../rollup.config.mjs | 35 ++ .../jdrupes/vmoperator/vmviewer/VmViewer.java | 454 +++++++++++++++ .../vmoperator/vmviewer/VmViewerFactory.java | 54 ++ .../vmviewer/browser/VmViewer-functions.ts | 206 +++++++ .../vmviewer/browser/VmViewer-style.scss | 42 ++ .../vmviewer/browser/l10nBundles-stub.d.ts | 1 + .../vmoperator/vmviewer/package-info.java | 19 + org.jdrupes.vmoperator.vmviewer/tsconfig.json | 23 + settings.gradle | 1 + 77 files changed, 2642 insertions(+), 250 deletions(-) create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sClusterGenericStub.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1NodeStub.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ServiceStub.java rename org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/{DisplaySecretChanged.java => DisplayPasswordChanged.java} (89%) create mode 100644 org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/GetDisplayPassword.java create mode 100644 org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ServiceChanged.java create mode 100644 org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplayPasswordMonitor.java rename org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/{DisplaySecretsMonitor.java => ServiceMonitor.java} (71%) create mode 100644 org.jdrupes.vmoperator.runner.qemu/password-expiry create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpSetPasswordExpiry.java create mode 100644 org.jdrupes.vmoperator.vmviewer/.checkstyle create mode 100644 org.jdrupes.vmoperator.vmviewer/.eclipse-pmd create mode 100644 org.jdrupes.vmoperator.vmviewer/.eslintignore create mode 100644 org.jdrupes.vmoperator.vmviewer/.eslintrc.json create mode 100644 org.jdrupes.vmoperator.vmviewer/.gitignore create mode 100644 org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.buildship.core.prefs create mode 100644 org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.core.resources.prefs create mode 100644 org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.core.runtime.prefs create mode 100644 org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.jdt.ui.prefs create mode 100644 org.jdrupes.vmoperator.vmviewer/build.gradle create mode 100644 org.jdrupes.vmoperator.vmviewer/package.json create mode 100644 org.jdrupes.vmoperator.vmviewer/resources/META-INF/services/org.jgrapes.webconsole.base.ConletComponentFactory create mode 100644 org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-edit.ftl.html create mode 100644 org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-l10nBundles.ftl.js create mode 100644 org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-preview.ftl.html create mode 100644 org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/computer-off.svg create mode 100644 org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/computer.svg create mode 100644 org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n.properties create mode 100644 org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n_de.properties create mode 100644 org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n_en.properties create mode 100644 org.jdrupes.vmoperator.vmviewer/rollup.config.mjs create mode 100644 org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java create mode 100644 org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewerFactory.java create mode 100644 org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-functions.ts create mode 100644 org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-style.scss create mode 100644 org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/l10nBundles-stub.d.ts create mode 100644 org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/package-info.java create mode 100644 org.jdrupes.vmoperator.vmviewer/tsconfig.json diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs index 44c5061..72733d9 100644 --- a/.settings/org.eclipse.buildship.core.prefs +++ b/.settings/org.eclipse.buildship.core.prefs @@ -5,7 +5,7 @@ connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.project.dir= eclipse.preferences.version=1 gradle.user.home= -java.home=/usr/lib/jvm/java-17-openjdk-17.0.8.0.7-1.fc37.x86_64 +java.home= jvm.arguments= offline.mode=false override.workspace.settings=true diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index e4db3d5..de65f8a 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -1385,9 +1385,20 @@ spec: type: object properties: port: + description: >- + Port number used for the Spice server. type: integer default: 5900 - ticket: + server: + description: >- + Server (address) to use for connecting + to the spice server. Defaults to the address + of the node that the VM is running on. + type: string + proxyUrl: + description: >- + If specified, is copied to the generated + viewer configuration files. type: string streamingVideo: type: string @@ -1413,7 +1424,8 @@ spec: default: "0" displayPasswordSerial: description: >- - Counts changes of the display password. + Counts changes of the display password. Set to -1 + by the runner if password protection is not enabled. type: integer default: 0 conditions: diff --git a/dev-example/config.yaml b/dev-example/config.yaml index 09183b4..3c94254 100644 --- a/dev-example/config.yaml +++ b/dev-example/config.yaml @@ -41,3 +41,7 @@ other: - --org.jdrupes.vmoperator.vmconlet.VmConlet - org.jgrapes.webconlet.oidclogin.LoginConlet + "/ComponentCollector": + "/VmViewer": + displayResource: + preferredIpVersion: ipv4 diff --git a/dev-example/kustomization.yaml b/dev-example/kustomization.yaml index 70c6ae6..9829bf6 100644 --- a/dev-example/kustomization.yaml +++ b/dev-example/kustomization.yaml @@ -66,7 +66,10 @@ patches: # Others cannot use any conlet (except login conlet to log out) other: - org.jgrapes.webconlet.locallogin.LoginConlet - + "/ComponentCollector": + "/VmViewer": + displayResource: + preferredIpVersion: ipv4 - target: group: apps version: v1 diff --git a/dev-example/test-vm-display-secret.yaml b/dev-example/test-vm-display-secret.yaml index f3b5ccb..a6f0fe6 100644 --- a/dev-example/test-vm-display-secret.yaml +++ b/dev-example/test-vm-display-secret.yaml @@ -10,3 +10,4 @@ metadata: type: Opaque data: display-password: dGVzdC12bQ== + password-expiry: KzMw diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java index d5d457c..150bb3b 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/Constants.java @@ -21,6 +21,7 @@ package org.jdrupes.vmoperator.common; /** * Some constants. */ +@SuppressWarnings("PMD.DataClass") public class Constants { /** The Constant APP_NAME. */ diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java index 2c29fab..481f724 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8s.java @@ -44,7 +44,6 @@ import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; -// TODO: Auto-generated Javadoc /** * Helpers for K8s API. */ @@ -168,6 +167,7 @@ public class K8s { * @return the object */ @Deprecated + @SuppressWarnings("PMD.GenericsNaming") public static Optional get(GenericKubernetesApi api, V1ObjectMeta meta) { @@ -189,6 +189,7 @@ public class K8s { * @return the t * @throws ApiException the api exception */ + @SuppressWarnings("PMD.GenericsNaming") public static T apply(GenericKubernetesApi api, T existing, String update) throws ApiException { diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sClient.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sClient.java index b7106fb..37b0b97 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sClient.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sClient.java @@ -48,7 +48,8 @@ import okhttp3.Response; * A client with some additional properties. */ @SuppressWarnings({ "PMD.ExcessivePublicCount", "PMD.TooManyMethods", - "PMD.LinguisticNaming", "checkstyle:LineLength" }) + "PMD.LinguisticNaming", "checkstyle:LineLength", + "PMD.CouplingBetweenObjects", "PMD.GodClass" }) public class K8sClient extends ApiClient { private ApiClient apiClient; @@ -96,381 +97,510 @@ public class K8sClient extends ApiClient { } /** - * @return + * Gets the base path. + * + * @return the base path * @see ApiClient#getBasePath() */ + @Override public String getBasePath() { return apiClient().getBasePath(); } /** - * @param basePath - * @return + * Sets the base path. + * + * @param basePath the base path + * @return the api client * @see ApiClient#setBasePath(java.lang.String) */ + @Override public ApiClient setBasePath(String basePath) { return apiClient().setBasePath(basePath); } /** - * @return + * Gets the http client. + * + * @return the http client * @see ApiClient#getHttpClient() */ + @Override public OkHttpClient getHttpClient() { return apiClient().getHttpClient(); } /** - * @param newHttpClient - * @return + * Sets the http client. + * + * @param newHttpClient the new http client + * @return the api client * @see ApiClient#setHttpClient(okhttp3.OkHttpClient) */ + @Override public ApiClient setHttpClient(OkHttpClient newHttpClient) { return apiClient().setHttpClient(newHttpClient); } /** - * @return + * Gets the json. + * + * @return the json * @see ApiClient#getJSON() */ @SuppressWarnings("abbreviationAsWordInName") + @Override public JSON getJSON() { return apiClient().getJSON(); } /** - * @param json - * @return + * Sets the JSON. + * + * @param json the json + * @return the api client * @see ApiClient#setJSON(io.kubernetes.client.openapi.JSON) */ @SuppressWarnings("abbreviationAsWordInName") + @Override public ApiClient setJSON(JSON json) { return apiClient().setJSON(json); } /** - * @return + * Checks if is verifying ssl. + * + * @return true, if is verifying ssl * @see ApiClient#isVerifyingSsl() */ + @Override public boolean isVerifyingSsl() { return apiClient().isVerifyingSsl(); } /** - * @param verifyingSsl - * @return + * Sets the verifying ssl. + * + * @param verifyingSsl the verifying ssl + * @return the api client * @see ApiClient#setVerifyingSsl(boolean) */ + @Override public ApiClient setVerifyingSsl(boolean verifyingSsl) { return apiClient().setVerifyingSsl(verifyingSsl); } /** - * @return + * Gets the ssl ca cert. + * + * @return the ssl ca cert * @see ApiClient#getSslCaCert() */ + @Override public InputStream getSslCaCert() { return apiClient().getSslCaCert(); } /** - * @param sslCaCert - * @return + * Sets the ssl ca cert. + * + * @param sslCaCert the ssl ca cert + * @return the api client * @see ApiClient#setSslCaCert(java.io.InputStream) */ + @Override public ApiClient setSslCaCert(InputStream sslCaCert) { return apiClient().setSslCaCert(sslCaCert); } /** - * @return + * Gets the key managers. + * + * @return the key managers * @see ApiClient#getKeyManagers() */ + @Override public KeyManager[] getKeyManagers() { return apiClient().getKeyManagers(); } /** - * @param managers - * @return + * Sets the key managers. + * + * @param managers the managers + * @return the api client * @see ApiClient#setKeyManagers(javax.net.ssl.KeyManager[]) */ @SuppressWarnings("PMD.UseVarargs") + @Override public ApiClient setKeyManagers(KeyManager[] managers) { return apiClient().setKeyManagers(managers); } /** - * @return + * Gets the date format. + * + * @return the date format * @see ApiClient#getDateFormat() */ + @Override public DateFormat getDateFormat() { return apiClient().getDateFormat(); } /** - * @param dateFormat - * @return + * Sets the date format. + * + * @param dateFormat the date format + * @return the api client * @see ApiClient#setDateFormat(java.text.DateFormat) */ + @Override public ApiClient setDateFormat(DateFormat dateFormat) { return apiClient().setDateFormat(dateFormat); } /** - * @param dateFormat - * @return + * Sets the sql date format. + * + * @param dateFormat the date format + * @return the api client * @see ApiClient#setSqlDateFormat(java.text.DateFormat) */ + @Override public ApiClient setSqlDateFormat(DateFormat dateFormat) { return apiClient().setSqlDateFormat(dateFormat); } /** - * @param dateFormat - * @return + * Sets the offset date time format. + * + * @param dateFormat the date format + * @return the api client * @see ApiClient#setOffsetDateTimeFormat(java.time.format.DateTimeFormatter) */ + @Override public ApiClient setOffsetDateTimeFormat(DateTimeFormatter dateFormat) { return apiClient().setOffsetDateTimeFormat(dateFormat); } /** - * @param dateFormat - * @return + * Sets the local date format. + * + * @param dateFormat the date format + * @return the api client * @see ApiClient#setLocalDateFormat(java.time.format.DateTimeFormatter) */ + @Override public ApiClient setLocalDateFormat(DateTimeFormatter dateFormat) { return apiClient().setLocalDateFormat(dateFormat); } /** - * @param lenientOnJson - * @return + * Sets the lenient on json. + * + * @param lenientOnJson the lenient on json + * @return the api client * @see ApiClient#setLenientOnJson(boolean) */ + @Override public ApiClient setLenientOnJson(boolean lenientOnJson) { return apiClient().setLenientOnJson(lenientOnJson); } /** - * @return + * Gets the authentications. + * + * @return the authentications * @see ApiClient#getAuthentications() */ + @Override public Map getAuthentications() { return apiClient().getAuthentications(); } /** - * @param authName - * @return + * Gets the authentication. + * + * @param authName the auth name + * @return the authentication * @see ApiClient#getAuthentication(java.lang.String) */ + @Override public Authentication getAuthentication(String authName) { return apiClient().getAuthentication(authName); } /** - * @param username + * Sets the username. + * + * @param username the new username * @see ApiClient#setUsername(java.lang.String) */ + @Override public void setUsername(String username) { apiClient().setUsername(username); } /** - * @param password + * Sets the password. + * + * @param password the new password * @see ApiClient#setPassword(java.lang.String) */ + @Override public void setPassword(String password) { apiClient().setPassword(password); } /** - * @param apiKey + * Sets the api key. + * + * @param apiKey the new api key * @see ApiClient#setApiKey(java.lang.String) */ + @Override public void setApiKey(String apiKey) { apiClient().setApiKey(apiKey); } /** - * @param apiKeyPrefix + * Sets the api key prefix. + * + * @param apiKeyPrefix the new api key prefix * @see ApiClient#setApiKeyPrefix(java.lang.String) */ + @Override public void setApiKeyPrefix(String apiKeyPrefix) { apiClient().setApiKeyPrefix(apiKeyPrefix); } /** - * @param accessToken + * Sets the access token. + * + * @param accessToken the new access token * @see ApiClient#setAccessToken(java.lang.String) */ + @Override public void setAccessToken(String accessToken) { apiClient().setAccessToken(accessToken); } /** - * @param userAgent - * @return + * Sets the user agent. + * + * @param userAgent the user agent + * @return the api client * @see ApiClient#setUserAgent(java.lang.String) */ + @Override public ApiClient setUserAgent(String userAgent) { return apiClient().setUserAgent(userAgent); } /** - * @return + * To string. + * + * @return the string * @see java.lang.Object#toString() */ + @Override public String toString() { return apiClient().toString(); } /** - * @param key - * @param value - * @return + * Adds the default header. + * + * @param key the key + * @param value the value + * @return the api client * @see ApiClient#addDefaultHeader(java.lang.String, java.lang.String) */ + @Override public ApiClient addDefaultHeader(String key, String value) { return apiClient().addDefaultHeader(key, value); } /** - * @param key - * @param value - * @return + * Adds the default cookie. + * + * @param key the key + * @param value the value + * @return the api client * @see ApiClient#addDefaultCookie(java.lang.String, java.lang.String) */ + @Override public ApiClient addDefaultCookie(String key, String value) { return apiClient().addDefaultCookie(key, value); } /** - * @return + * Checks if is debugging. + * + * @return true, if is debugging * @see ApiClient#isDebugging() */ + @Override public boolean isDebugging() { return apiClient().isDebugging(); } /** - * @param debugging - * @return + * Sets the debugging. + * + * @param debugging the debugging + * @return the api client * @see ApiClient#setDebugging(boolean) */ + @Override public ApiClient setDebugging(boolean debugging) { return apiClient().setDebugging(debugging); } /** - * @return + * Gets the temp folder path. + * + * @return the temp folder path * @see ApiClient#getTempFolderPath() */ + @Override public String getTempFolderPath() { return apiClient().getTempFolderPath(); } /** - * @param tempFolderPath - * @return + * Sets the temp folder path. + * + * @param tempFolderPath the temp folder path + * @return the api client * @see ApiClient#setTempFolderPath(java.lang.String) */ + @Override public ApiClient setTempFolderPath(String tempFolderPath) { return apiClient().setTempFolderPath(tempFolderPath); } /** - * @return + * Gets the connect timeout. + * + * @return the connect timeout * @see ApiClient#getConnectTimeout() */ + @Override public int getConnectTimeout() { return apiClient().getConnectTimeout(); } /** - * @param connectionTimeout - * @return + * Sets the connect timeout. + * + * @param connectionTimeout the connection timeout + * @return the api client * @see ApiClient#setConnectTimeout(int) */ + @Override public ApiClient setConnectTimeout(int connectionTimeout) { return apiClient().setConnectTimeout(connectionTimeout); } /** - * @return + * Gets the read timeout. + * + * @return the read timeout * @see ApiClient#getReadTimeout() */ + @Override public int getReadTimeout() { return apiClient().getReadTimeout(); } /** - * @param readTimeout - * @return + * Sets the read timeout. + * + * @param readTimeout the read timeout + * @return the api client * @see ApiClient#setReadTimeout(int) */ + @Override public ApiClient setReadTimeout(int readTimeout) { return apiClient().setReadTimeout(readTimeout); } /** - * @return + * Gets the write timeout. + * + * @return the write timeout * @see ApiClient#getWriteTimeout() */ + @Override public int getWriteTimeout() { return apiClient().getWriteTimeout(); } /** - * @param writeTimeout - * @return + * Sets the write timeout. + * + * @param writeTimeout the write timeout + * @return the api client * @see ApiClient#setWriteTimeout(int) */ + @Override public ApiClient setWriteTimeout(int writeTimeout) { return apiClient().setWriteTimeout(writeTimeout); } /** - * @param param - * @return + * Parameter to string. + * + * @param param the param + * @return the string * @see ApiClient#parameterToString(java.lang.Object) */ + @Override public String parameterToString(Object param) { return apiClient().parameterToString(param); } /** - * @param name - * @param value - * @return + * Parameter to pair. + * + * @param name the name + * @param value the value + * @return the list * @see ApiClient#parameterToPair(java.lang.String, java.lang.Object) */ + @Override public List parameterToPair(String name, Object value) { return apiClient().parameterToPair(name, value); } /** - * @param collectionFormat - * @param name - * @param value - * @return + * Parameter to pairs. + * + * @param collectionFormat the collection format + * @param name the name + * @param value the value + * @return the list * @see ApiClient#parameterToPairs(java.lang.String, java.lang.String, java.util.Collection) */ @SuppressWarnings({ "rawtypes", "PMD.AvoidDuplicateLiterals" }) + @Override public List parameterToPairs(String collectionFormat, String name, Collection value) { return apiClient().parameterToPairs(collectionFormat, name, value); } /** - * @param collectionFormat - * @param value - * @return + * Collection path parameter to string. + * + * @param collectionFormat the collection format + * @param value the value + * @return the string * @see ApiClient#collectionPathParameterToString(java.lang.String, java.util.Collection) */ @SuppressWarnings("rawtypes") + @Override public String collectionPathParameterToString(String collectionFormat, Collection value) { return apiClient().collectionPathParameterToString(collectionFormat, @@ -478,173 +608,218 @@ public class K8sClient extends ApiClient { } /** - * @param filename - * @return + * Sanitize filename. + * + * @param filename the filename + * @return the string * @see ApiClient#sanitizeFilename(java.lang.String) */ + @Override public String sanitizeFilename(String filename) { return apiClient().sanitizeFilename(filename); } /** - * @param mime - * @return + * Checks if is json mime. + * + * @param mime the mime + * @return true, if is json mime * @see ApiClient#isJsonMime(java.lang.String) */ + @Override public boolean isJsonMime(String mime) { return apiClient().isJsonMime(mime); } /** - * @param accepts - * @return + * Select header accept. + * + * @param accepts the accepts + * @return the string * @see ApiClient#selectHeaderAccept(java.lang.String[]) */ @SuppressWarnings("PMD.UseVarargs") + @Override public String selectHeaderAccept(String[] accepts) { return apiClient().selectHeaderAccept(accepts); } /** - * @param contentTypes - * @return + * Select header content type. + * + * @param contentTypes the content types + * @return the string * @see ApiClient#selectHeaderContentType(java.lang.String[]) */ @SuppressWarnings("PMD.UseVarargs") + @Override public String selectHeaderContentType(String[] contentTypes) { return apiClient().selectHeaderContentType(contentTypes); } /** - * @param str - * @return + * Escape string. + * + * @param str the str + * @return the string * @see ApiClient#escapeString(java.lang.String) */ + @Override public String escapeString(String str) { return apiClient().escapeString(str); } /** - * @param - * @param response - * @param returnType - * @return - * @throws ApiException + * Deserialize. + * + * @param the generic type + * @param response the response + * @param returnType the return type + * @return the t + * @throws ApiException the api exception * @see ApiClient#deserialize(okhttp3.Response, java.lang.reflect.Type) */ + @Override public T deserialize(Response response, Type returnType) throws ApiException { return apiClient().deserialize(response, returnType); } /** - * @param obj - * @param contentType - * @return - * @throws ApiException + * Serialize. + * + * @param obj the obj + * @param contentType the content type + * @return the request body + * @throws ApiException the api exception * @see ApiClient#serialize(java.lang.Object, java.lang.String) */ + @Override public RequestBody serialize(Object obj, String contentType) throws ApiException { return apiClient().serialize(obj, contentType); } /** - * @param response - * @return - * @throws ApiException + * Download file from response. + * + * @param response the response + * @return the file + * @throws ApiException the api exception * @see ApiClient#downloadFileFromResponse(okhttp3.Response) */ + @Override public File downloadFileFromResponse(Response response) throws ApiException { return apiClient().downloadFileFromResponse(response); } /** - * @param response - * @return - * @throws IOException + * Prepare download file. + * + * @param response the response + * @return the file + * @throws IOException Signals that an I/O exception has occurred. * @see ApiClient#prepareDownloadFile(okhttp3.Response) */ + @Override public File prepareDownloadFile(Response response) throws IOException { return apiClient().prepareDownloadFile(response); } /** - * @param - * @param call - * @return - * @throws ApiException + * Execute. + * + * @param the generic type + * @param call the call + * @return the api response + * @throws ApiException the api exception * @see ApiClient#execute(okhttp3.Call) */ + @Override public ApiResponse execute(Call call) throws ApiException { return apiClient().execute(call); } /** - * @param - * @param call - * @param returnType - * @return - * @throws ApiException + * Execute. + * + * @param the generic type + * @param call the call + * @param returnType the return type + * @return the api response + * @throws ApiException the api exception * @see ApiClient#execute(okhttp3.Call, java.lang.reflect.Type) */ + @Override public ApiResponse execute(Call call, Type returnType) throws ApiException { return apiClient().execute(call, returnType); } /** - * @param - * @param call - * @param callback + * Execute async. + * + * @param the generic type + * @param call the call + * @param callback the callback * @see ApiClient#executeAsync(okhttp3.Call, io.kubernetes.client.openapi.ApiCallback) */ + @Override public void executeAsync(Call call, ApiCallback callback) { apiClient().executeAsync(call, callback); } /** - * @param - * @param call - * @param returnType - * @param callback + * Execute async. + * + * @param the generic type + * @param call the call + * @param returnType the return type + * @param callback the callback * @see ApiClient#executeAsync(okhttp3.Call, java.lang.reflect.Type, io.kubernetes.client.openapi.ApiCallback) */ + @Override public void executeAsync(Call call, Type returnType, ApiCallback callback) { apiClient().executeAsync(call, returnType, callback); } /** - * @param - * @param response - * @param returnType - * @return - * @throws ApiException + * Handle response. + * + * @param the generic type + * @param response the response + * @param returnType the return type + * @return the t + * @throws ApiException the api exception * @see ApiClient#handleResponse(okhttp3.Response, java.lang.reflect.Type) */ + @Override public T handleResponse(Response response, Type returnType) throws ApiException { return apiClient().handleResponse(response, returnType); } /** - * @param path - * @param method - * @param queryParams - * @param collectionQueryParams - * @param body - * @param headerParams - * @param cookieParams - * @param formParams - * @param authNames - * @param callback - * @return - * @throws ApiException + * Builds the call. + * + * @param path the path + * @param method the method + * @param queryParams the query params + * @param collectionQueryParams the collection query params + * @param body the body + * @param headerParams the header params + * @param cookieParams the cookie params + * @param formParams the form params + * @param authNames the auth names + * @param callback the callback + * @return the call + * @throws ApiException the api exception * @see ApiClient#buildCall(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.Object, java.util.Map, java.util.Map, java.util.Map, java.lang.String[], io.kubernetes.client.openapi.ApiCallback) */ @SuppressWarnings({ "rawtypes", "PMD.ExcessiveParameterList" }) + @Override public Call buildCall(String path, String method, List queryParams, List collectionQueryParams, Object body, Map headerParams, Map cookieParams, @@ -656,21 +831,24 @@ public class K8sClient extends ApiClient { } /** - * @param path - * @param method - * @param queryParams - * @param collectionQueryParams - * @param body - * @param headerParams - * @param cookieParams - * @param formParams - * @param authNames - * @param callback - * @return - * @throws ApiException + * Builds the request. + * + * @param path the path + * @param method the method + * @param queryParams the query params + * @param collectionQueryParams the collection query params + * @param body the body + * @param headerParams the header params + * @param cookieParams the cookie params + * @param formParams the form params + * @param authNames the auth names + * @param callback the callback + * @return the request + * @throws ApiException the api exception * @see ApiClient#buildRequest(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.Object, java.util.Map, java.util.Map, java.util.Map, java.lang.String[], io.kubernetes.client.openapi.ApiCallback) */ @SuppressWarnings({ "rawtypes", "PMD.ExcessiveParameterList" }) + @Override public Request buildRequest(String path, String method, List queryParams, List collectionQueryParams, Object body, Map headerParams, @@ -682,44 +860,56 @@ public class K8sClient extends ApiClient { } /** - * @param path - * @param queryParams - * @param collectionQueryParams - * @return + * Builds the url. + * + * @param path the path + * @param queryParams the query params + * @param collectionQueryParams the collection query params + * @return the string * @see ApiClient#buildUrl(java.lang.String, java.util.List, java.util.List) */ + @Override public String buildUrl(String path, List queryParams, List collectionQueryParams) { return apiClient().buildUrl(path, queryParams, collectionQueryParams); } /** - * @param headerParams - * @param reqBuilder + * Process header params. + * + * @param headerParams the header params + * @param reqBuilder the req builder * @see ApiClient#processHeaderParams(java.util.Map, okhttp3.Request.Builder) */ + @Override public void processHeaderParams(Map headerParams, Builder reqBuilder) { apiClient().processHeaderParams(headerParams, reqBuilder); } /** - * @param cookieParams - * @param reqBuilder + * Process cookie params. + * + * @param cookieParams the cookie params + * @param reqBuilder the req builder * @see ApiClient#processCookieParams(java.util.Map, okhttp3.Request.Builder) */ + @Override public void processCookieParams(Map cookieParams, Builder reqBuilder) { apiClient().processCookieParams(cookieParams, reqBuilder); } /** - * @param authNames - * @param queryParams - * @param headerParams - * @param cookieParams + * Update params for auth. + * + * @param authNames the auth names + * @param queryParams the query params + * @param headerParams the header params + * @param cookieParams the cookie params * @see ApiClient#updateParamsForAuth(java.lang.String[], java.util.List, java.util.Map, java.util.Map) */ + @Override public void updateParamsForAuth(String[] authNames, List queryParams, Map headerParams, Map cookieParams) { @@ -728,30 +918,39 @@ public class K8sClient extends ApiClient { } /** - * @param formParams - * @return + * Builds the request body form encoding. + * + * @param formParams the form params + * @return the request body * @see ApiClient#buildRequestBodyFormEncoding(java.util.Map) */ + @Override public RequestBody buildRequestBodyFormEncoding(Map formParams) { return apiClient().buildRequestBodyFormEncoding(formParams); } /** - * @param formParams - * @return + * Builds the request body multipart. + * + * @param formParams the form params + * @return the request body * @see ApiClient#buildRequestBodyMultipart(java.util.Map) */ + @Override public RequestBody buildRequestBodyMultipart(Map formParams) { return apiClient().buildRequestBodyMultipart(formParams); } /** - * @param file - * @return + * Guess content type from file. + * + * @param file the file + * @return the string * @see ApiClient#guessContentTypeFromFile(java.io.File) */ + @Override public String guessContentTypeFromFile(File file) { return apiClient().guessContentTypeFromFile(file); } diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sClusterGenericStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sClusterGenericStub.java new file mode 100644 index 0000000..81a4eab --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sClusterGenericStub.java @@ -0,0 +1,401 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import io.kubernetes.client.Discovery.APIResource; +import io.kubernetes.client.apimachinery.GroupVersionKind; +import io.kubernetes.client.common.KubernetesListObject; +import io.kubernetes.client.common.KubernetesObject; +import io.kubernetes.client.custom.V1Patch; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.util.Strings; +import io.kubernetes.client.util.generic.GenericKubernetesApi; +import io.kubernetes.client.util.generic.options.GetOptions; +import io.kubernetes.client.util.generic.options.ListOptions; +import io.kubernetes.client.util.generic.options.PatchOptions; +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +/** + * A stub for cluster scoped objects. This stub provides the + * functions common to all Kubernetes objects, but uses variables + * for all types. This class should be used as base class only. + * + * @param the generic type + * @param the generic type + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class K8sClusterGenericStub { + protected final K8sClient client; + private final GenericKubernetesApi api; + protected final APIResource context; + protected final String name; + + /** + * Instantiates a new stub for the object specified. If the object + * exists in the context specified, the version (see + * {@link #version()} is bound to the existing object's version. + * Else the stub is dangling with the version set to the context's + * preferred version. + * + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the context + * @param name the name + */ + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + protected K8sClusterGenericStub(Class objectClass, + Class objectListClass, K8sClient client, APIResource context, + String name) { + this.client = client; + this.name = name; + + // Bind version + var foundVersion = context.getPreferredVersion(); + GenericKubernetesApi testApi = null; + GetOptions mdOpts + = new GetOptions().isPartialObjectMetadataRequest(true); + for (var version : candidateVersions(context)) { + testApi = new GenericKubernetesApi<>(objectClass, objectListClass, + context.getGroup(), version, context.getResourcePlural(), + client); + if (testApi.get(name, mdOpts).isSuccess()) { + foundVersion = version; + break; + } + } + if (foundVersion.equals(context.getPreferredVersion())) { + this.context = context; + } else { + this.context = K8s.preferred(context, foundVersion); + } + + api = Optional.ofNullable(testApi) + .orElseGet(() -> new GenericKubernetesApi<>(objectClass, + objectListClass, group(), version(), plural(), client)); + } + + /** + * Gets the context. + * + * @return the context + */ + public APIResource context() { + return context; + } + + /** + * Gets the group. + * + * @return the group + */ + public String group() { + return context.getGroup(); + } + + /** + * Gets the version. + * + * @return the version + */ + public String version() { + return context.getPreferredVersion(); + } + + /** + * Gets the kind. + * + * @return the kind + */ + public String kind() { + return context.getKind(); + } + + /** + * Gets the plural. + * + * @return the plural + */ + public String plural() { + return context.getResourcePlural(); + } + + /** + * Gets the name. + * + * @return the name + */ + public String name() { + return name; + } + + /** + * Delete the Kubernetes object. + * + * @throws ApiException the API exception + */ + public void delete() throws ApiException { + var result = api.delete(name); + if (result.isSuccess() + || result.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { + return; + } + result.throwsApiException(); + } + + /** + * Retrieves and returns the current state of the object. + * + * @return the object's state + * @throws ApiException the api exception + */ + public Optional model() throws ApiException { + return K8s.optional(api.get(name)); + } + + /** + * Updates the object's status. + * + * @param object the current state of the object (passed to `status`) + * @param status function that returns the new status + * @return the updated model or empty if not successful + * @throws ApiException the api exception + */ + public Optional updateStatus(O object, + Function status) throws ApiException { + return K8s.optional(api.updateStatus(object, status)); + } + + /** + * Updates the status. + * + * @param status the status + * @return the kubernetes api response + * the updated model or empty if not successful + * @throws ApiException the api exception + */ + public Optional updateStatus(Function status) + throws ApiException { + return updateStatus(api.get(name).throwsApiException().getObject(), + status); + } + + /** + * Patch the object. + * + * @param patchType the patch type + * @param patch the patch + * @param options the options + * @return the kubernetes api response + * @throws ApiException the api exception + */ + public Optional patch(String patchType, V1Patch patch, + PatchOptions options) throws ApiException { + return K8s + .optional(api.patch(name, patchType, patch, options)); + } + + /** + * Patch the object using default options. + * + * @param patchType the patch type + * @param patch the patch + * @return the kubernetes api response + * @throws ApiException the api exception + */ + public Optional + patch(String patchType, V1Patch patch) throws ApiException { + PatchOptions opts = new PatchOptions(); + return patch(patchType, patch, opts); + } + + /** + * A supplier for generic stubs. + * + * @param the object type + * @param the object list type + * @param the result type + */ + public interface GenericSupplier> { + + /** + * Gets a new stub. + * + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the API resource + * @param name the name + * @return the result + */ + @SuppressWarnings("PMD.UseObjectForClearerAPI") + R get(Class objectClass, Class objectListClass, K8sClient client, + APIResource context, String name); + } + + @Override + @SuppressWarnings("PMD.UseLocaleWithCaseConversions") + public String toString() { + return (Strings.isNullOrEmpty(group()) ? "" : group() + "/") + + version().toUpperCase() + kind() + " " + name; + } + + /** + * Get an object stub. If the version in parameter + * `gvk` is an empty string, the stub refers to the first object + * found with matching group and kind. + * + * @param the object type + * @param the object list type + * @param the stub type + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param gvk the group, version and kind + * @param name the name + * @param provider the provider + * @return the stub if the object exists + * @throws ApiException the api exception + */ + @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop" }) + public static > + R get(Class objectClass, Class objectListClass, + K8sClient client, GroupVersionKind gvk, String name, + GenericSupplier provider) throws ApiException { + var context = K8s.context(client, gvk.getGroup(), gvk.getVersion(), + gvk.getKind()); + if (context.isEmpty()) { + throw new ApiException("No known API for " + gvk.getGroup() + + "/" + gvk.getVersion() + " " + gvk.getKind()); + } + return provider.get(objectClass, objectListClass, client, context.get(), + name); + } + + /** + * Get an object stub. + * + * @param the object type + * @param the object list type + * @param the stub type + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the context + * @param name the name + * @param provider the provider + * @return the stub if the object exists + * @throws ApiException the api exception + */ + @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", + "PMD.UseObjectForClearerAPI" }) + public static > + R get(Class objectClass, Class objectListClass, + K8sClient client, APIResource context, String name, + GenericSupplier provider) { + return provider.get(objectClass, objectListClass, client, context, + name); + } + + /** + * Get an object stub for a newly created object. + * + * @param the object type + * @param the object list type + * @param the stub type + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the context + * @param model the model + * @param provider the provider + * @return the stub if the object exists + * @throws ApiException the api exception + */ + @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", + "PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" }) + public static > + R create(Class objectClass, Class objectListClass, + K8sClient client, APIResource context, O model, + GenericSupplier provider) throws ApiException { + var api = new GenericKubernetesApi<>(objectClass, objectListClass, + context.getGroup(), context.getPreferredVersion(), + context.getResourcePlural(), client); + api.create(model).throwsApiException(); + return provider.get(objectClass, objectListClass, client, + context, model.getMetadata().getName()); + } + + /** + * Get the stubs for the objects that match + * the criteria from the given options. + * + * @param the object type + * @param the object list type + * @param the stub type + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the context + * @param options the options + * @param provider the provider + * @return the collection + * @throws ApiException the api exception + */ + public static > + Collection list(Class objectClass, Class objectListClass, + K8sClient client, APIResource context, + ListOptions options, GenericSupplier provider) + throws ApiException { + var result = new ArrayList(); + for (var version : candidateVersions(context)) { + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + var api = new GenericKubernetesApi<>(objectClass, objectListClass, + context.getGroup(), version, context.getResourcePlural(), + client); + var objs = api.list(options).throwsApiException(); + for (var item : objs.getObject().getItems()) { + result.add(provider.get(objectClass, objectListClass, client, + context, item.getMetadata().getName())); + } + } + return result; + } + + private static List candidateVersions(APIResource context) { + var result = new LinkedList<>(context.getVersions()); + result.remove(context.getPreferredVersion()); + result.add(0, context.getPreferredVersion()); + return result; + } + +} diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelTypeAdapterFactory.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelTypeAdapterFactory.java index 9018744..33a8e18 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelTypeAdapterFactory.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelTypeAdapterFactory.java @@ -44,12 +44,13 @@ public class K8sDynamicModelTypeAdapterFactory implements TypeAdapterFactory { * this factory */ @SuppressWarnings("unchecked") + @Override public TypeAdapter create(Gson gson, TypeToken typeToken) { if (TypeToken.get(K8sDynamicModel.class).equals(typeToken)) { - return (TypeAdapter) (new K8sDynamicModelCreator(gson)); + return (TypeAdapter) new K8sDynamicModelCreator(gson); } if (TypeToken.get(K8sDynamicModels.class).equals(typeToken)) { - return (TypeAdapter) (new K8sDynamicModelsCreator(gson)); + return (TypeAdapter) new K8sDynamicModelsCreator(gson); } return null; } diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sObserver.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sObserver.java index 148ce83..2545a30 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sObserver.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sObserver.java @@ -87,7 +87,7 @@ public class K8sObserver { try { - logger.info(() -> "Watching " + context.getResourcePlural() + logger.config(() -> "Watching " + context.getResourcePlural() + " (" + context.getPreferredVersion() + ")" + " in " + namespace); diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1NodeStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1NodeStub.java new file mode 100644 index 0000000..050c593 --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1NodeStub.java @@ -0,0 +1,85 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import io.kubernetes.client.Discovery.APIResource; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.models.V1Node; +import io.kubernetes.client.openapi.models.V1NodeList; +import io.kubernetes.client.util.generic.options.ListOptions; +import java.util.Collection; +import java.util.List; + +/** + * A stub for nodes (v1). + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class K8sV1NodeStub extends K8sClusterGenericStub { + + public static final APIResource CONTEXT = new APIResource("", List.of("v1"), + "v1", "Node", true, "nodes", "node"); + + /** + * Instantiates a new stub. + * + * @param client the client + * @param name the name + */ + protected K8sV1NodeStub(K8sClient client, String name) { + super(V1Node.class, V1NodeList.class, client, CONTEXT, name); + } + + /** + * Gets the stub for the given name. + * + * @param client the client + * @param name the name + * @return the config map stub + */ + public static K8sV1NodeStub get(K8sClient client, String name) { + return new K8sV1NodeStub(client, name); + } + + /** + * Get the stubs for the objects that match + * the criteria from the given options. + * + * @param client the client + * @param options the options + * @return the collection + * @throws ApiException the api exception + */ + public static Collection list(K8sClient client, + ListOptions options) throws ApiException { + return K8sClusterGenericStub.list(V1Node.class, V1NodeList.class, + client, CONTEXT, options, K8sV1NodeStub::getGeneric); + } + + /** + * Provide {@link GenericSupplier}. + */ + @SuppressWarnings({ "PMD.UnusedFormalParameter", + "PMD.UnusedPrivateMethod" }) + private static K8sV1NodeStub getGeneric(Class objectClass, + Class objectListClass, K8sClient client, + APIResource context, String name) { + return new K8sV1NodeStub(client, name); + } + +} \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1PodStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1PodStub.java index b3da776..1cea986 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1PodStub.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1PodStub.java @@ -79,7 +79,8 @@ public class K8sV1PodStub extends K8sGenericStub { /** * Provide {@link GenericSupplier}. */ - @SuppressWarnings("PMD.UnusedFormalParameter") + @SuppressWarnings({ "PMD.UnusedFormalParameter", + "PMD.UnusedPrivateMethod" }) private static K8sV1PodStub getGeneric(Class objectClass, Class objectListClass, K8sClient client, APIResource context, String namespace, String name) { diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java index 18973e0..9a43883 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java @@ -81,7 +81,8 @@ public class K8sV1SecretStub extends K8sGenericStub { /** * Provide {@link GenericSupplier}. */ - @SuppressWarnings("PMD.UnusedFormalParameter") + @SuppressWarnings({ "PMD.UnusedFormalParameter", + "PMD.UnusedPrivateMethod" }) private static K8sV1SecretStub getGeneric(Class objectClass, Class objectListClass, K8sClient client, APIResource context, String namespace, String name) { diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ServiceStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ServiceStub.java new file mode 100644 index 0000000..74f7f61 --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ServiceStub.java @@ -0,0 +1,92 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import io.kubernetes.client.Discovery.APIResource; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.models.V1Service; +import io.kubernetes.client.openapi.models.V1ServiceList; +import io.kubernetes.client.util.generic.options.ListOptions; +import java.util.Collection; +import java.util.List; +import org.jdrupes.vmoperator.common.K8sGenericStub.GenericSupplier; + +/** + * A stub for secrets (v1). + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class K8sV1ServiceStub extends K8sGenericStub { + + public static final APIResource CONTEXT = new APIResource("", List.of("v1"), + "v1", "Service", true, "services", "service"); + + /** + * Instantiates a new stub. + * + * @param client the client + * @param namespace the namespace + * @param name the name + */ + protected K8sV1ServiceStub(K8sClient client, String namespace, + String name) { + super(V1Service.class, V1ServiceList.class, client, CONTEXT, namespace, + name); + } + + /** + * Gets the stub for the given namespace and name. + * + * @param client the client + * @param namespace the namespace + * @param name the name + * @return the config map stub + */ + public static K8sV1ServiceStub get(K8sClient client, String namespace, + String name) { + return new K8sV1ServiceStub(client, namespace, name); + } + + /** + * Get the stubs for the objects in the given namespace that match + * the criteria from the given options. + * + * @param client the client + * @param namespace the namespace + * @param options the options + * @return the collection + * @throws ApiException the api exception + */ + public static Collection list(K8sClient client, + String namespace, ListOptions options) throws ApiException { + return K8sGenericStub.list(V1Service.class, V1ServiceList.class, client, + CONTEXT, namespace, options, K8sV1ServiceStub::getGeneric); + } + + /** + * Provide {@link GenericSupplier}. + */ + @SuppressWarnings({ "PMD.UnusedFormalParameter", + "PMD.UnusedPrivateMethod" }) + private static K8sV1ServiceStub getGeneric(Class objectClass, + Class objectListClass, K8sClient client, + APIResource context, String namespace, String name) { + return new K8sV1ServiceStub(client, namespace, name); + } + +} \ No newline at end of file diff --git a/org.jdrupes.vmoperator.manager.events/build.gradle b/org.jdrupes.vmoperator.manager.events/build.gradle index 56c364f..566e200 100644 --- a/org.jdrupes.vmoperator.manager.events/build.gradle +++ b/org.jdrupes.vmoperator.manager.events/build.gradle @@ -11,4 +11,5 @@ plugins { dependencies { api 'org.jgrapes:org.jgrapes.core:[1.19.0,2)' api project(':org.jdrupes.vmoperator.common') + api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:[2.16.1,3]' } diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ChannelCache.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ChannelCache.java index fc8df7a..1e6d031 100644 --- a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ChannelCache.java +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ChannelCache.java @@ -44,7 +44,7 @@ public class ChannelCache { */ @SuppressWarnings("PMD.ShortClassName") private static class Data { - public WeakReference channel; + public final WeakReference channel; public A associated; /** diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/DisplaySecretChanged.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/DisplayPasswordChanged.java similarity index 89% rename from org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/DisplaySecretChanged.java rename to org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/DisplayPasswordChanged.java index 69dabe6..9185bbc 100644 --- a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/DisplaySecretChanged.java +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/DisplayPasswordChanged.java @@ -28,7 +28,7 @@ import org.jgrapes.core.Event; * Indicates that a display secret has changed. */ @SuppressWarnings("PMD.DataClass") -public class DisplaySecretChanged extends Event { +public class DisplayPasswordChanged extends Event { private final ResponseType type; private final V1Secret secret; @@ -39,7 +39,7 @@ public class DisplaySecretChanged extends Event { * @param type the type * @param secret the secret */ - public DisplaySecretChanged(ResponseType type, V1Secret secret) { + public DisplayPasswordChanged(ResponseType type, V1Secret secret) { this.type = type; this.secret = secret; } @@ -68,8 +68,7 @@ public class DisplaySecretChanged extends Event { builder.append(Components.objectName(this)).append(" [") .append(secret.getMetadata().getName()).append(' ').append(type); if (channels() != null) { - builder.append(", channels="); - builder.append(Channel.toString(channels())); + builder.append(", channels=").append(Channel.toString(channels())); } builder.append(']'); return builder.toString(); diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/GetDisplayPassword.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/GetDisplayPassword.java new file mode 100644 index 0000000..77dc298 --- /dev/null +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/GetDisplayPassword.java @@ -0,0 +1,58 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.manager.events; + +import java.util.Optional; +import org.jgrapes.core.Event; + +/** + * Gets the current display secret. + */ +@SuppressWarnings("PMD.DataClass") +public class GetDisplayPassword extends Event { + + private final String vmName; + + /** + * Instantiates a new returns the display secret. + * + * @param vmName the vm name + */ + public GetDisplayPassword(String vmName) { + this.vmName = vmName; + } + + /** + * Gets the vm name. + * + * @return the vm name + */ + public String vmName() { + return vmName; + } + + /** + * Return the password. Should only be called when the event is completed. + * + * @return the optional + */ + public Optional password() { + return currentResults().stream().findFirst(); + } +} diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ServiceChanged.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ServiceChanged.java new file mode 100644 index 0000000..a8008e0 --- /dev/null +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ServiceChanged.java @@ -0,0 +1,76 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.manager.events; + +import io.kubernetes.client.openapi.models.V1Service; +import org.jdrupes.vmoperator.common.K8sObserver.ResponseType; +import org.jgrapes.core.Channel; +import org.jgrapes.core.Components; +import org.jgrapes.core.Event; + +/** + * Indicates that a service has changed. + */ +@SuppressWarnings("PMD.DataClass") +public class ServiceChanged extends Event { + + private final ResponseType type; + private final V1Service service; + + /** + * Initializes a new service changed event. + * + * @param type the type + * @param service the service + */ + public ServiceChanged(ResponseType type, V1Service service) { + this.type = type; + this.service = service; + } + + /** + * Returns the type. + * + * @return the type + */ + public ResponseType type() { + return type; + } + + /** + * Gets the service. + * + * @return the service + */ + public V1Service service() { + return service; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(Components.objectName(this)).append(" [") + .append(service.getMetadata().getName()).append(' ').append(type); + if (channels() != null) { + builder.append(", channels=").append(Channel.toString(channels())); + } + builder.append(']'); + return builder.toString(); + } +} diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java index d4a1aa9..5e93790 100644 --- a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java @@ -83,8 +83,7 @@ public class VmDefChanged extends Event { builder.append(Components.objectName(this)).append(" [") .append(vmDef.getMetadata().getName()).append(' ').append(type); if (channels() != null) { - builder.append(", channels="); - builder.append(Channel.toString(channels())); + builder.append(", channels=").append(Channel.toString(channels())); } builder.append(']'); return builder.toString(); diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index 3018d0a..7dc07a2 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -32,6 +32,7 @@ dependencies { runtimeOnly 'org.apache.logging.log4j:log4j-to-jul:2.20.0' runtimeOnly project(':org.jdrupes.vmoperator.vmconlet') + runtimeOnly project(':org.jdrupes.vmoperator.vmviewer') } application { diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml index 451a465..7679a68 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml @@ -202,7 +202,7 @@ data: ticket: "${ cr.spec.vm.display.spice.ticket.asString }" <#if cr.spec.vm.display.spice.streamingVideo??> - ticket: "${ cr.spec.vm.display.spice.streamingVideo.asString }" + streaming-video: "${ cr.spec.vm.display.spice.streamingVideo.asString }" usbRedirects: ${ cr.spec.vm.display.spice.usbRedirects.asInt?c } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java index 0fbb3a7..0c5f0cd 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java @@ -111,7 +111,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; .list(newCm.getMetadata().getNamespace(), listOpts).getObject(); // If the VM is being created, the pod may not exist yet. - if (pods == null || pods.getItems().size() == 0) { + if (pods == null || pods.getItems().isEmpty()) { return; } var pod = pods.getItems().get(0); diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java index 17e74bf..a7b84a3 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java @@ -21,6 +21,7 @@ package org.jdrupes.vmoperator.manager; /** * Some constants. */ +@SuppressWarnings("PMD.DataClass") public class Constants extends org.jdrupes.vmoperator.common.Constants { /** The Constant COMP_DISPLAY_SECRET. */ diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java index cf47f9c..89b5eac 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java @@ -85,6 +85,7 @@ public class Controller extends Component { /** * Creates a new instance. */ + @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") public Controller(Channel componentChannel) { super(componentChannel); // Prepare component tree @@ -100,8 +101,11 @@ public class Controller extends Component { } }); attach(new VmMonitor(channel()).channelManager(chanMgr)); - attach(new DisplaySecretsMonitor(channel()) + attach(new DisplayPasswordMonitor(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. + // attach(new ServiceMonitor(channel()).channelManager(chanMgr)); attach(new Reconciler(channel())); } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplayPasswordMonitor.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplayPasswordMonitor.java new file mode 100644 index 0000000..9959aec --- /dev/null +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplayPasswordMonitor.java @@ -0,0 +1,102 @@ +/* + * 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 . + */ + +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 { + + /** + * 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 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))); + } +} diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index e0fdca0..5ba9dc5 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -224,6 +224,7 @@ public class Reconciler extends Component { return new DynamicKubernetesObject(json); } + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") private void adjustCdRomPaths(JsonObject json) { var disks = GsonPtr.to(json).to("spec", "vm", "disks").get(JsonArray.class); diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretsMonitor.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ServiceMonitor.java similarity index 71% rename from org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretsMonitor.java rename to org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ServiceMonitor.java index de51080..bd5635e 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretsMonitor.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ServiceMonitor.java @@ -19,38 +19,36 @@ 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.openapi.models.V1Service; +import io.kubernetes.client.openapi.models.V1ServiceList; 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.DisplaySecretChanged; +import org.jdrupes.vmoperator.common.K8sV1ServiceStub; +import org.jdrupes.vmoperator.manager.events.ServiceChanged; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jgrapes.core.Channel; /** - * Watches for changes of display secrets. + * Watches for changes of services. */ @SuppressWarnings("PMD.DataflowAnomalyAnalysis") -public class DisplaySecretsMonitor - extends AbstractMonitor { +public class ServiceMonitor + extends AbstractMonitor { /** * Instantiates a new display secrets monitor. * * @param componentChannel the component channel */ - public DisplaySecretsMonitor(Channel componentChannel) { - super(componentChannel, V1Secret.class, V1SecretList.class); - context(K8sV1SecretStub.CONTEXT); + public ServiceMonitor(Channel componentChannel) { + super(componentChannel, V1Service.class, V1ServiceList.class); + context(K8sV1ServiceStub.CONTEXT); ListOptions options = new ListOptions(); - options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME + "," - + "app.kubernetes.io/component=" + COMP_DISPLAY_SECRET); + options.setLabelSelector("app.kubernetes.io/name=" + APP_NAME); options(options); } @@ -60,7 +58,7 @@ public class DisplaySecretsMonitor } @Override - protected void handleChange(K8sClient client, Response change) { + protected void handleChange(K8sClient client, Response change) { String vmName = change.object.getMetadata().getLabels() .get("app.kubernetes.io/instance"); if (vmName == null) { @@ -70,8 +68,7 @@ public class DisplaySecretsMonitor if (channel == null || channel.vmDefinition() == null) { return; } - channel.pipeline().fire(new DisplaySecretChanged( + channel.pipeline().fire(new ServiceChanged( ResponseType.valueOf(change.type), change.object), channel); } - } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java index 8812a93..baf833c 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/StatefulSetReconciler.java @@ -82,14 +82,15 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; // or not running. var stsStub = K8sV1StatefulSetStub.get(channel.client(), metadata.getNamespace(), metadata.getName()); - stsStub.model().ifPresent(sts -> { - var current = sts.getSpec().getReplicas(); + var stsModel = stsStub.model().orElse(null); + if (stsModel != null) { + var current = stsModel.getSpec().getReplicas(); var desired = GsonPtr.to(stsDef.getRaw()) .to("spec").getAsInt("replicas").orElse(1); if (current == 1 && desired == 1) { return; } - }); + } // Do apply changes PatchOptions opts = new PatchOptions(); diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java index b12dc48..7027808 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java @@ -150,6 +150,7 @@ public class VmMonitor private void addDynamicData(K8sClient client, K8sDynamicModel vmState) { var rootNode = GsonPtr.to(vmState.data()).get(JsonObject.class); rootNode.addProperty("nodeName", ""); + rootNode.addProperty("nodeAddress", ""); // VM definition status changes before the pod terminates. // This results in pod information being shown for a stopped @@ -172,8 +173,17 @@ public class VmMonitor var podList = K8sV1PodStub.list(client, namespace(), podSearch); for (var podStub : podList) { - rootNode.addProperty("nodeName", - podStub.model().get().getSpec().getNodeName()); + var nodeName = podStub.model().get().getSpec().getNodeName(); + rootNode.addProperty("nodeName", nodeName); + logger.fine(() -> "Added node name " + nodeName + + " to VM info for " + vmState.getMetadata().getName()); + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + var addrs = new JsonArray(); + podStub.model().get().getStatus().getPodIPs().stream() + .map(ip -> ip.getIp()).forEach(addrs::add); + rootNode.add("nodeAddresses", addrs); + logger.fine(() -> "Added node addresses " + addrs + + " to VM info for " + vmState.getMetadata().getName()); } } catch (ApiException e) { logger.log(Level.WARNING, e, diff --git a/org.jdrupes.vmoperator.runner.qemu/password-expiry b/org.jdrupes.vmoperator.runner.qemu/password-expiry new file mode 100644 index 0000000..c42fe6e --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/password-expiry @@ -0,0 +1 @@ ++30 \ No newline at end of file diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java index b37d2c5..d6d5219 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java @@ -108,7 +108,7 @@ public class Configuration implements Dto { * Subsection "vm". */ @SuppressWarnings({ "PMD.ShortClassName", "PMD.TooManyFields", - "PMD.DataClass" }) + "PMD.DataClass", "PMD.AvoidDuplicateLiterals" }) public static class Vm implements Dto { /** The name. */ @@ -196,6 +196,7 @@ public class Configuration implements Dto { /** * Subsection "network". */ + @SuppressWarnings("PMD.DataClass") public static class Network implements Dto { /** The type. */ @@ -217,6 +218,7 @@ public class Configuration implements Dto { /** * Subsection "drive". */ + @SuppressWarnings("PMD.DataClass") public static class Drive implements Dto { /** The type. */ @@ -247,6 +249,7 @@ public class Configuration implements Dto { /** * Subsection "spice". */ + @SuppressWarnings("PMD.DataClass") public static class Spice implements Dto { /** The port. */ diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java index 3071e42..857b14e 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import org.jdrupes.vmoperator.runner.qemu.commands.QmpAddCpu; @@ -170,7 +171,7 @@ public class CpuController extends Component { private void checkCpus() { if (suspendedConfigure != null && desiredCpus != null - && currentCpus == desiredCpus.intValue()) { + && Objects.equals(currentCpus, desiredCpus)) { suspendedConfigure.resumeHandling(); suspendedConfigure = null; } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java index 882b85b..304ea04 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java @@ -24,6 +24,7 @@ import java.nio.file.Path; import java.util.Objects; import java.util.logging.Level; import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetDisplayPassword; +import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetPasswordExpiry; import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; @@ -40,6 +41,7 @@ import org.jgrapes.util.events.WatchFile; public class DisplayController extends Component { public static final String DISPLAY_PASSWORD_FILE = "display-password"; + public static final String PASSWORD_EXPIRY_FILE = "password-expiry"; private String currentPassword; private String protocol; private final Path configDir; @@ -50,7 +52,8 @@ public class DisplayController extends Component { * @param componentChannel the component channel * @param configDir */ - @SuppressWarnings("PMD.AssignmentToNonFinalStatic") + @SuppressWarnings({ "PMD.AssignmentToNonFinalStatic", + "PMD.ConstructorCallsOverridableMethod" }) public DisplayController(Channel componentChannel, Path configDir) { super(componentChannel); this.configDir = configDir; @@ -90,7 +93,12 @@ public class DisplayController extends Component { if (protocol == null) { return; } + if (setDisplayPassword()) { + setPasswordExpiry(); + } + } + private boolean setDisplayPassword() { String password; Path dpPath = configDir.resolve(DISPLAY_PASSWORD_FILE); if (dpPath.toFile().canRead()) { @@ -100,18 +108,37 @@ public class DisplayController extends Component { } catch (IOException e) { logger.log(Level.WARNING, e, () -> "Cannot read display" + " password: " + e.getMessage()); - return; + return false; } } else { logger.finer(() -> "No display password"); - return; + return false; } if (Objects.equals(this.currentPassword, password)) { - return; + return false; } logger.fine(() -> "Updating display password"); fire(new MonitorCommand(new QmpSetDisplayPassword(protocol, password))); + return true; + } + + private void setPasswordExpiry() { + Path pePath = configDir.resolve(PASSWORD_EXPIRY_FILE); + if (!pePath.toFile().canRead()) { + return; + } + logger.finer(() -> "Found expiry time"); + String expiry; + try { + expiry = Files.readString(pePath); + } catch (IOException e) { + logger.log(Level.WARNING, e, () -> "Cannot read expiry" + + " time: " + e.getMessage()); + return; + } + logger.fine(() -> "Updating expiry time"); + fire(new MonitorCommand(new QmpSetPasswordExpiry(protocol, expiry))); } } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java index 84a99a4..f59375c 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/QemuMonitor.java @@ -90,7 +90,8 @@ public class QemuMonitor extends Component { * @param configDir the config dir * @throws IOException Signals that an I/O exception has occurred. */ - @SuppressWarnings("PMD.AssignmentToNonFinalStatic") + @SuppressWarnings({ "PMD.AssignmentToNonFinalStatic", + "PMD.ConstructorCallsOverridableMethod" }) public QemuMonitor(Channel componentChannel, Path configDir) throws IOException { super(componentChannel); @@ -155,6 +156,7 @@ public class QemuMonitor extends Component { * @param event the event * @param channel the channel */ + @SuppressWarnings("resource") @Handler public void onClientConnected(ClientConnected event, SocketIOChannel channel) { @@ -276,7 +278,7 @@ public class QemuMonitor extends Component { writer.append(asText).append('\n').flush(); } catch (IOException e) { // Cannot happen, but... - logger.log(Level.WARNING, e, () -> e.getMessage()); + logger.log(Level.WARNING, e, e::getMessage); } }); } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index 624d949..e0baa4f 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -186,7 +186,8 @@ import org.jgrapes.util.events.WatchFile; * */ @SuppressWarnings({ "PMD.ExcessiveImports", "PMD.AvoidPrintStackTrace", - "PMD.DataflowAnomalyAnalysis", "PMD.TooManyMethods" }) + "PMD.DataflowAnomalyAnalysis", "PMD.TooManyMethods", + "PMD.CouplingBetweenObjects" }) public class Runner extends Component { private static final String QEMU = "qemu"; @@ -232,7 +233,8 @@ public class Runner extends Component { * @param cmdLine the cmd line * @throws IOException Signals that an I/O exception has occurred. */ - @SuppressWarnings("PMD.SystemPrintln") + @SuppressWarnings({ "PMD.SystemPrintln", + "PMD.ConstructorCallsOverridableMethod" }) public Runner(CommandLine cmdLine) throws IOException { yamlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); @@ -495,27 +497,27 @@ public class Runner extends Component { try { var cloudInitDir = config.dataDir.resolve("cloud-init"); cloudInitDir.toFile().mkdir(); - var metaOut - = Files.newBufferedWriter(cloudInitDir.resolve("meta-data")); - if (config.cloudInit.metaData != null) { - yamlMapper.writer().writeValue(metaOut, - config.cloudInit.metaData); + try (var metaOut + = Files.newBufferedWriter(cloudInitDir.resolve("meta-data"))) { + if (config.cloudInit.metaData != null) { + yamlMapper.writer().writeValue(metaOut, + config.cloudInit.metaData); + } } - metaOut.close(); - var userOut - = Files.newBufferedWriter(cloudInitDir.resolve("user-data")); - userOut.write("#cloud-config\n"); - if (config.cloudInit.userData != null) { - yamlMapper.writer().writeValue(userOut, - config.cloudInit.userData); + try (var userOut + = Files.newBufferedWriter(cloudInitDir.resolve("user-data"))) { + userOut.write("#cloud-config\n"); + if (config.cloudInit.userData != null) { + yamlMapper.writer().writeValue(userOut, + config.cloudInit.userData); + } } - userOut.close(); if (config.cloudInit.networkConfig != null) { - var networkConfig = Files.newBufferedWriter( - cloudInitDir.resolve("network-config")); - yamlMapper.writer().writeValue(networkConfig, - config.cloudInit.networkConfig); - networkConfig.close(); + try (var networkConfig = Files.newBufferedWriter( + cloudInitDir.resolve("network-config"))) { + yamlMapper.writer().writeValue(networkConfig, + config.cloudInit.networkConfig); + } } startProcess(cloudInitImgDefinition); } catch (IOException e) { @@ -545,7 +547,6 @@ public class Runner extends Component { && event.path().equals(config.swtpmSocket)) { // swtpm running, maybe start qemu mayBeStartQemu(QemuPreps.Tpm); - return; } } @@ -690,6 +691,7 @@ public class Runner extends Component { "The VM has been shut down")); } + @SuppressWarnings("PMD.ConfusingArgumentToVarargsMethod") private void shutdown() { if (!Set.of(State.TERMINATING, State.STOPPED).contains(state)) { fire(new Stop()); diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index bb15639..06ed64c 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -80,6 +80,7 @@ public class StatusUpdater extends Component { * * @param componentChannel the component channel */ + @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") public StatusUpdater(Channel componentChannel) { super(componentChannel); try { @@ -91,7 +92,6 @@ public class StatusUpdater extends Component { () -> "Cannot access events API, terminating."); fire(new Exit(1)); } - } /** @@ -179,6 +179,7 @@ public class StatusUpdater extends Component { * @throws ApiException */ @Handler + @SuppressWarnings("PMD.AvoidDuplicateLiterals") public void onConfigureQemu(ConfigureQemu event) throws ApiException { guestShutdownStops = event.configuration().guestShutdownStops; @@ -189,14 +190,22 @@ public class StatusUpdater extends Component { } // A change of the runner configuration is typically caused - // by a new version of the CR. So we observe the new CR. + // by a new version of the CR. So we update only if we have + // a new version of the CR. There's one exception: the display + // password is configured by a file, not by the CR. var vmDef = vmStub.model(); if (vmDef.isPresent() - && vmDef.get().metadata().getGeneration() == observedGeneration) { + && vmDef.get().metadata().getGeneration() == observedGeneration + && (event.configuration().hasDisplayPassword + || vmDef.get().status().getAsJsonPrimitive( + "displayPasswordSerial").getAsInt() == -1)) { return; } vmStub.updateStatus(vmDef.get(), from -> { JsonObject status = from.status(); + if (!event.configuration().hasDisplayPassword) { + status.addProperty("displayPasswordSerial", -1); + } status.getAsJsonArray("conditions").asList().stream() .map(cond -> (JsonObject) cond).filter(cond -> "Running" .equals(cond.get("type").getAsString())) @@ -213,7 +222,8 @@ public class StatusUpdater extends Component { * @throws ApiException */ @Handler - @SuppressWarnings("PMD.AssignmentInOperand") + @SuppressWarnings({ "PMD.AssignmentInOperand", + "PMD.AvoidLiteralsInIfCondition" }) public void onRunnerStateChanged(RunnerStateChange event) throws ApiException { K8sDynamicModel vmDef; diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpAddCpu.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpAddCpu.java index e77a984..86d92f6 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpAddCpu.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpAddCpu.java @@ -47,7 +47,7 @@ public class QmpAddCpu extends QmpCommand { cmd.put("execute", "device_add"); ObjectNode args = mapper.createObjectNode(); cmd.set("arguments", args); - args.setAll((ObjectNode) (unused.get("props"))); + args.setAll((ObjectNode) unused.get("props")); args.set("driver", unused.get("type")); args.put("id", cpuId); return cmd; diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpSetPasswordExpiry.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpSetPasswordExpiry.java new file mode 100644 index 0000000..672767d --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpSetPasswordExpiry.java @@ -0,0 +1,66 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.runner.qemu.commands; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; + +/** + * A {@link QmpCommand} that sets the password expiry. + */ +public class QmpSetPasswordExpiry extends QmpCommand { + + private final String protocol; + private final String expiry; + + /** + * Instantiates a new command. + * + * @param protocol the protocol + * @param expiry the expiry time + */ + public QmpSetPasswordExpiry(String protocol, String expiry) { + this.protocol = protocol; + this.expiry = expiry; + } + + @Override + public JsonNode toJson() { + ObjectNode cmd = mapper.createObjectNode(); + cmd.put("execute", "expire_password"); + ObjectNode args = mapper.createObjectNode(); + cmd.set("arguments", args); + args.set("protocol", new TextNode(protocol)); + args.set("time", new TextNode(expiry)); + return cmd; + } + + @Override + public String toString() { + try { + var json = toJson(); + return mapper.writeValueAsString(json); + } catch (JsonProcessingException e) { + return "(no string representation)"; + } + } + +} diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/HotpluggableCpuStatus.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/HotpluggableCpuStatus.java index 68641c9..2ab2c5a 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/HotpluggableCpuStatus.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/HotpluggableCpuStatus.java @@ -30,7 +30,9 @@ import org.jdrupes.vmoperator.runner.qemu.commands.QmpCommand; */ public class HotpluggableCpuStatus extends MonitorResult { + @SuppressWarnings("PMD.ImmutableField") private List usedCpus = new ArrayList<>(); + @SuppressWarnings("PMD.ImmutableField") private List unusedCpus = new ArrayList<>(); /** diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorCommand.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorCommand.java index 36d5b40..d2b5e8c 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorCommand.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorCommand.java @@ -55,8 +55,7 @@ public class MonitorCommand extends Event { builder.append(Components.objectName(this)) .append(" [").append(command); if (channels() != null) { - builder.append(", channels="); - builder.append(Channel.toString(channels())); + builder.append(", channels=").append(Channel.toString(channels())); } builder.append(']'); return builder.toString(); diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java index 9352ab2..6d7278c 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/MonitorResult.java @@ -152,8 +152,7 @@ public class MonitorResult extends Event { builder.append(Components.objectName(this)) .append(" [").append(executed).append(", ").append(successful()); if (channels() != null) { - builder.append(", channels="); - builder.append(Channel.toString(channels())); + builder.append(", channels=").append(Channel.toString(channels())); } builder.append(']'); return builder.toString(); diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java index 5d5bffd..4a7ef08 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java @@ -109,15 +109,14 @@ public class RunnerStateChange extends Event { @Override public String toString() { - StringBuilder builder = new StringBuilder(); + StringBuilder builder = new StringBuilder(50); builder.append(Components.objectName(this)) .append(" [").append(state).append(": ").append(reason); if (failed) { builder.append(" (failed)"); } if (channels() != null) { - builder.append(", channels="); - builder.append(Channel.toString(channels())); + builder.append(", channels=").append(Channel.toString(channels())); } builder.append(']'); return builder.toString(); diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n.properties b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n.properties index 9a0efcf..880369b 100644 --- a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n.properties +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n.properties @@ -1,4 +1,4 @@ -conletName = VM Viewer +conletName = VM Infos VMsSummary = VMs (running/total) diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties index 1804b81..7e1d95e 100644 --- a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/l10n_de.properties @@ -1,4 +1,4 @@ -conletName = VM Anzeige +conletName = VM-Informationen VMsSummary = VMs (gestartet/gesamt) diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java index 025ce3c..1882173 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java @@ -86,6 +86,7 @@ public class VmConlet extends FreeMarkerConlet { * on by default and that {@link Manager#fire(Event, Channel...)} * sends the event to */ + @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") public VmConlet(Channel componentChannel) { super(componentChannel); setPeriodicRefresh(Duration.ofMinutes(1), () -> new Update()); @@ -138,7 +139,7 @@ public class VmConlet extends FreeMarkerConlet { .setRenderAs( RenderMode.Preview.addModifiers(event.renderAs())) .setSupportedModes(MODES)); - renderedAs.add(RenderMode.View); + renderedAs.add(RenderMode.Preview); channel.respond(new NotifyConletView(type(), conletId, "summarySeries", summarySeries.entries())); var summary = evaluateSummary(false); @@ -181,7 +182,8 @@ public class VmConlet extends FreeMarkerConlet { */ @Handler(namedChannels = "manager") @SuppressWarnings({ "PMD.ConfusingTernary", "PMD.CognitiveComplexity", - "PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals" }) + "PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals", + "PMD.ConfusingArgumentToVarargsMethod" }) public void onVmDefChanged(VmDefChanged event, VmChannel channel) throws JsonDecodeException, IOException { var vmName = event.vmDefinition().getMetadata().getName(); diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss index 8f85eb6..3649bff 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss @@ -97,7 +97,11 @@ .jdrupes-vmoperator-vmconlet-view-action-list { white-space: nowrap; - [role=button]:not(:last-child) { - margin-right: 0.5em; + [role=button] { + padding: 0.25rem; + + &:not([aria-disabled]):hover { + box-shadow: var(--darkening); + } } } diff --git a/org.jdrupes.vmoperator.vmviewer/.checkstyle b/org.jdrupes.vmoperator.vmviewer/.checkstyle new file mode 100644 index 0000000..7f2c604 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/.checkstyle @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/org.jdrupes.vmoperator.vmviewer/.eclipse-pmd b/org.jdrupes.vmoperator.vmviewer/.eclipse-pmd new file mode 100644 index 0000000..5d69caa --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/.eclipse-pmd @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.jdrupes.vmoperator.vmviewer/.eslintignore b/org.jdrupes.vmoperator.vmviewer/.eslintignore new file mode 100644 index 0000000..139d3ee --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/.eslintignore @@ -0,0 +1 @@ +rollup.config.mjs diff --git a/org.jdrupes.vmoperator.vmviewer/.eslintrc.json b/org.jdrupes.vmoperator.vmviewer/.eslintrc.json new file mode 100644 index 0000000..e4f80f1 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/.eslintrc.json @@ -0,0 +1,15 @@ +{ + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { "project": ["./tsconfig.json"] }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "constructor-super": "off" + } +} + diff --git a/org.jdrupes.vmoperator.vmviewer/.gitignore b/org.jdrupes.vmoperator.vmviewer/.gitignore new file mode 100644 index 0000000..a53e74c --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/.gitignore @@ -0,0 +1,4 @@ +/bin/ +/bin_test/ +/generated/ +/build/ diff --git a/org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.buildship.core.prefs b/org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000..641c156 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,10 @@ +build.commands=org.eclipse.jdt.core.javabuilder +connection.arguments= +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) +connection.java.home=null +connection.jvm.arguments= +connection.project.dir=.. +derived.resources=.gradle,generated +eclipse.preferences.version=1 +natures=org.eclipse.jdt.groovy.core.groovyNature,org.eclipse.jdt.core.javanature +project.path=\:org.jgrapes.osgi.conlets.services diff --git a/org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.core.resources.prefs b/org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.core.runtime.prefs b/org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 0000000..5a0ad22 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +line.separator=\n diff --git a/org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.jdt.ui.prefs b/org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..784d01f --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,63 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_JGrapes +formatter_settings_version=13 +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.make_local_variable_final=true +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=false +sp_cleanup.organize_imports=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_type_arguments=false +sp_cleanup.remove_trailing_whitespaces=false +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_lambda=true +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true +sp_jautodoc.cleanup.add_header=false +sp_jautodoc.cleanup.replace_header=false diff --git a/org.jdrupes.vmoperator.vmviewer/build.gradle b/org.jdrupes.vmoperator.vmviewer/build.gradle new file mode 100644 index 0000000..ab667f5 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/build.gradle @@ -0,0 +1,57 @@ +plugins { + id 'org.jdrupes.vmoperator.java-library-conventions' +} + +dependencies { + implementation project(':org.jdrupes.vmoperator.manager.events') + + implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.3.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.provider.vue:[1,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.provider.jgwcvuecomponents:[1.2,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.provider.chartjs:[1.2,2)' + +} + +apply plugin: 'com.github.node-gradle.node' + +node { + download = true +} + +task extractDependencies(type: Copy) { + from configurations.compileClasspath + .findAll{ it.name.contains('.provider.') + || it.name.contains('org.jgrapes.webconsole.base') + } + .collect{ zipTree (it) } + exclude '*.class' + into 'build/unpacked' + duplicatesStrategy 'include' + } + +task compileTs(type: NodeTask) { + dependsOn ':npmInstall' + dependsOn extractDependencies + inputs.dir project.file('src') + inputs.file project.file('tsconfig.json') + inputs.file project.file('rollup.config.mjs') + outputs.dir project.file('build/generated/resources') + script = file("${rootProject.rootDir}/node_modules/rollup/dist/bin/rollup") + args = ["-c"] +} + +sourceSets { + main { + resources { + srcDir project.file('build/generated/resources') + } + } +} + +processResources { + dependsOn compileTs +} + +eclipse { + autoBuildTasks compileTs +} diff --git a/org.jdrupes.vmoperator.vmviewer/package.json b/org.jdrupes.vmoperator.vmviewer/package.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/package.json @@ -0,0 +1 @@ +{} diff --git a/org.jdrupes.vmoperator.vmviewer/resources/META-INF/services/org.jgrapes.webconsole.base.ConletComponentFactory b/org.jdrupes.vmoperator.vmviewer/resources/META-INF/services/org.jgrapes.webconsole.base.ConletComponentFactory new file mode 100644 index 0000000..ebe4408 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/resources/META-INF/services/org.jgrapes.webconsole.base.ConletComponentFactory @@ -0,0 +1 @@ +org.jdrupes.vmoperator.vmviewer.VmViewerFactory diff --git a/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-edit.ftl.html b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-edit.ftl.html new file mode 100644 index 0000000..d4e86ca --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-edit.ftl.html @@ -0,0 +1,20 @@ +
+
+
+ {{ localize("Select VM") }} +

+ +

+
+ +
diff --git a/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-l10nBundles.ftl.js b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-l10nBundles.ftl.js new file mode 100644 index 0000000..96928ef --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-l10nBundles.ftl.js @@ -0,0 +1,31 @@ +/* + * 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 . + */ + +"use strict"; + +const l10nBundles = new Map(); +let entries = null; +// <#list supportedLanguages() as l> +entries = new Map(); +l10nBundles.set("${l.locale.toLanguageTag()}", entries); +// <#list l.l10nBundle.keys as key> +entries.set("${key}", "${l.l10nBundle.getString(key)}"); +// +// + +export default l10nBundles; diff --git a/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-preview.ftl.html b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-preview.ftl.html new file mode 100644 index 0000000..1cd0392 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-preview.ftl.html @@ -0,0 +1,6 @@ +
+
diff --git a/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/computer-off.svg b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/computer-off.svg new file mode 100644 index 0000000..27c11ae --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/computer-off.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + diff --git a/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/computer.svg b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/computer.svg new file mode 100644 index 0000000..f7a6b94 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/computer.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + diff --git a/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n.properties b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n.properties new file mode 100644 index 0000000..96b0219 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n.properties @@ -0,0 +1,3 @@ +conletName = VM Console + +okayLabel = Apply and Close diff --git a/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n_de.properties b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n_de.properties new file mode 100644 index 0000000..e81a0fe --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n_de.properties @@ -0,0 +1,7 @@ +conletName = VM-Konsole + +okayLabel = Anwenden und Schließen +Select\ VM = VM auswählen + +Start\ VM = VM Starten +Stop\ VM = VM Anhalten diff --git a/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n_en.properties b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n_en.properties new file mode 100644 index 0000000..e69de29 diff --git a/org.jdrupes.vmoperator.vmviewer/rollup.config.mjs b/org.jdrupes.vmoperator.vmviewer/rollup.config.mjs new file mode 100644 index 0000000..f00a51f --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/rollup.config.mjs @@ -0,0 +1,35 @@ +import typescript from 'rollup-plugin-typescript2'; +import postcss from 'rollup-plugin-postcss'; + +let packagePath = "org/jdrupes/vmoperator/vmviewer"; +let baseName = "VmViewer" +let module = "build/generated/resources/" + packagePath + + "/" + baseName + "-functions.js"; + +let pathsMap = { + "aash-plugin": "../../page-resource/aash-vue-components/lib/aash-vue-components.js", + "jgconsole": "../../console-base-resource/jgconsole.js", + "jgwc": "../../page-resource/jgwc-vue-components/jgwc-components.js", + "l10nBundles": "./" + baseName + "-l10nBundles.ftl.js", + "vue": "../../page-resource/vue/vue.esm-browser.js" +} + +export default { + external: ['aash-plugin', 'jgconsole', 'jgwc', 'l10nBundles', 'vue', 'chartjs'], + input: "src/" + packagePath + "/browser/" + baseName + "-functions.ts", + output: [ + { + format: "esm", + file: module, + sourcemap: true, + sourcemapPathTransform: (relativeSourcePath, _sourcemapPath) => { + return relativeSourcePath.replace(/^([^/]*\/){12}/, "./"); + }, + paths: pathsMap + } + ], + plugins: [ + typescript(), + postcss() + ] +}; diff --git a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java new file mode 100644 index 0000000..b97ff6f --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java @@ -0,0 +1,454 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.vmviewer; + +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.google.gson.JsonPrimitive; +import freemarker.core.ParseException; +import freemarker.template.MalformedTemplateNameException; +import freemarker.template.Template; +import freemarker.template.TemplateNotFoundException; +import io.kubernetes.client.util.Strings; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Base64; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.logging.Level; +import org.jdrupes.json.JsonBeanDecoder; +import org.jdrupes.json.JsonDecodeException; +import org.jdrupes.vmoperator.common.K8sDynamicModel; +import org.jdrupes.vmoperator.common.K8sObserver; +import org.jdrupes.vmoperator.manager.events.ChannelCache; +import org.jdrupes.vmoperator.manager.events.GetDisplayPassword; +import org.jdrupes.vmoperator.manager.events.ModifyVm; +import org.jdrupes.vmoperator.manager.events.VmChannel; +import org.jdrupes.vmoperator.manager.events.VmDefChanged; +import org.jdrupes.vmoperator.util.GsonPtr; +import org.jgrapes.core.Channel; +import org.jgrapes.core.Event; +import org.jgrapes.core.Manager; +import org.jgrapes.core.annotation.Handler; +import org.jgrapes.http.Session; +import org.jgrapes.util.events.ConfigurationUpdate; +import org.jgrapes.util.events.KeyValueStoreQuery; +import org.jgrapes.util.events.KeyValueStoreUpdate; +import org.jgrapes.webconsole.base.Conlet.RenderMode; +import org.jgrapes.webconsole.base.ConletBaseModel; +import org.jgrapes.webconsole.base.ConsoleConnection; +import org.jgrapes.webconsole.base.ConsoleUser; +import org.jgrapes.webconsole.base.WebConsoleUtils; +import org.jgrapes.webconsole.base.events.AddConletType; +import org.jgrapes.webconsole.base.events.AddPageResources.ScriptResource; +import org.jgrapes.webconsole.base.events.ConletDeleted; +import org.jgrapes.webconsole.base.events.ConsoleReady; +import org.jgrapes.webconsole.base.events.DeleteConlet; +import org.jgrapes.webconsole.base.events.NotifyConletModel; +import org.jgrapes.webconsole.base.events.NotifyConletView; +import org.jgrapes.webconsole.base.events.OpenModalDialog; +import org.jgrapes.webconsole.base.events.RenderConlet; +import org.jgrapes.webconsole.base.events.RenderConletRequestBase; +import org.jgrapes.webconsole.base.events.SetLocale; +import org.jgrapes.webconsole.base.freemarker.FreeMarkerConlet; + +/** + * The Class VmConlet. + */ +@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports", + "PMD.CouplingBetweenObjects" }) +public class VmViewer extends FreeMarkerConlet { + + private static final Set MODES = RenderMode.asSet( + RenderMode.Preview, RenderMode.Edit); + private final ChannelCache channelManager = new ChannelCache<>(); + private static ObjectMapper objectMapper + = new ObjectMapper().registerModule(new JavaTimeModule()); + private Class preferredIpVersion = Inet4Address.class; + + /** + * The periodically generated update event. + */ + public static class Update extends Event { + } + + /** + * Creates a new component with its channel set to the given channel. + * + * @param componentChannel the channel that the component's handlers listen + * on by default and that {@link Manager#fire(Event, Channel...)} + * sends the event to + */ + public VmViewer(Channel componentChannel) { + super(componentChannel); + } + + /** + * Configure the component. + * + * @param event the event + */ + @Handler + public void onConfigurationUpdate(ConfigurationUpdate event) { + event.structured(componentPath()).ifPresent(c -> { + @SuppressWarnings("unchecked") + var dispRes = (Map) c + .getOrDefault("displayResource", Collections.emptyMap()); + switch ((String) dispRes.getOrDefault("preferredIpVersion", "")) { + case "ipv6": + preferredIpVersion = Inet6Address.class; + break; + case "ipv4": + default: + preferredIpVersion = Inet4Address.class; + break; + } + }); + } + + /** + * On {@link ConsoleReady}, fire the {@link AddConletType}. + * + * @param event the event + * @param channel the channel + * @throws TemplateNotFoundException the template not found exception + * @throws MalformedTemplateNameException the malformed template name + * exception + * @throws ParseException the parse exception + * @throws IOException Signals that an I/O exception has occurred. + */ + @Handler + public void onConsoleReady(ConsoleReady event, ConsoleConnection channel) + throws TemplateNotFoundException, MalformedTemplateNameException, + ParseException, IOException { + // Add conlet resources to page + channel.respond(new AddConletType(type()) + .setDisplayNames( + localizations(channel.supportedLocales(), "conletName")) + .addRenderMode(RenderMode.Preview) + .addScript(new ScriptResource().setScriptType("module") + .setScriptUri(event.renderSupport().conletResource( + type(), "VmViewer-functions.js")))); + } + + private String storagePath(Session session, String conletId) { + return "/" + WebConsoleUtils.userFromSession(session) + .map(ConsoleUser::getName).orElse("") + + "/" + VmViewer.class.getName() + "/" + conletId; + } + + @Override + protected Optional createStateRepresentation(Event event, + ConsoleConnection connection, String conletId) throws Exception { + var model = new ViewerModel(conletId); + String jsonState = objectMapper.writeValueAsString(model); + connection.respond(new KeyValueStoreUpdate().update( + storagePath(connection.session(), model.getConletId()), jsonState)); + return Optional.of(model); + } + + @Override + @SuppressWarnings("PMD.EmptyCatchBlock") + protected Optional recreateState(Event event, + ConsoleConnection channel, String conletId) throws Exception { + KeyValueStoreQuery query = new KeyValueStoreQuery( + storagePath(channel.session(), conletId), channel); + newEventPipeline().fire(query, channel); + try { + if (!query.results().isEmpty()) { + var json = query.results().get(0).values().stream().findFirst() + .get(); + ViewerModel model + = objectMapper.readValue(json, ViewerModel.class); + return Optional.of(model); + } + } catch (InterruptedException e) { + // Means we have no result. + } + + // Fall back to creating default state. + return createStateRepresentation(event, channel, conletId); + } + + @Override + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + protected Set doRenderConlet(RenderConletRequestBase event, + ConsoleConnection channel, String conletId, ViewerModel conletState) + throws Exception { + ResourceBundle resourceBundle = resourceBundle(channel.locale()); + Set renderedAs = new HashSet<>(); + if (event.renderAs().contains(RenderMode.Preview)) { + Template tpl + = freemarkerConfig().getTemplate("VmViewer-preview.ftl.html"); + channel.respond(new RenderConlet(type(), conletId, + processTemplate(event, tpl, + fmModel(event, channel, conletId, conletState))) + .setRenderAs( + RenderMode.Preview.addModifiers(event.renderAs())) + .setSupportedModes(MODES)); + renderedAs.add(RenderMode.Preview); + if (!Strings.isNullOrEmpty(conletState.vmName())) { + updateConfig(channel, conletState); + } + } + if (event.renderAs().contains(RenderMode.Edit)) { + Template tpl = freemarkerConfig() + .getTemplate("VmViewer-edit.ftl.html"); + var fmModel = fmModel(event, channel, conletId, conletState); + fmModel.put("vmNames", + channelManager.keys().stream().sorted().toList()); + channel.respond(new OpenModalDialog(type(), conletId, + processTemplate(event, tpl, fmModel)) + .addOption("cancelable", true) + .addOption("okayLabel", + resourceBundle.getString("okayLabel"))); + } + return renderedAs; + } + + private void updateConfig(ConsoleConnection channel, ViewerModel model) { + channel.respond(new NotifyConletView(type(), + model.getConletId(), "updateConfig", model.vmName())); + updateVmDef(channel, model); + } + + private void updateVmDef(ConsoleConnection channel, ViewerModel model) { + if (Strings.isNullOrEmpty(model.vmName())) { + return; + } + channelManager.associated(model.vmName()).ifPresent(vmDef -> { + try { + var def = JsonBeanDecoder.create(vmDef.data().toString()) + .readObject(); + channel.respond(new NotifyConletView(type(), + model.getConletId(), "updateVmDefinition", def)); + } catch (JsonDecodeException e) { + logger.log(Level.SEVERE, e, + () -> "Failed to serialize VM definition"); + } + }); + } + + @Override + protected void doConletDeleted(ConletDeleted event, + ConsoleConnection channel, String conletId, ViewerModel conletState) + throws Exception { + if (event.renderModes().isEmpty()) { + channel.respond(new KeyValueStoreUpdate().delete( + storagePath(channel.session(), conletId))); + } + } + + /** + * Track the VM definitions. + * + * @param event the event + * @param channel the channel + * @throws JsonDecodeException the json decode exception + * @throws IOException + */ + @Handler(namedChannels = "manager") + @SuppressWarnings({ "PMD.ConfusingTernary", "PMD.CognitiveComplexity", + "PMD.AvoidInstantiatingObjectsInLoops", "PMD.AvoidDuplicateLiterals", + "PMD.ConfusingArgumentToVarargsMethod" }) + public void onVmDefChanged(VmDefChanged event, VmChannel channel) + throws JsonDecodeException, IOException { + var vmDef = new K8sDynamicModel(channel.client().getJSON() + .getGson(), event.vmDefinition().data()); + var vmName = vmDef.getMetadata().getName(); + if (event.type() == K8sObserver.ResponseType.DELETED) { + channelManager.remove(vmName); + } else { + channelManager.put(vmName, channel, vmDef); + } + for (var entry : conletIdsByConsoleConnection().entrySet()) { + var connection = entry.getKey(); + for (var conletId : entry.getValue()) { + var model = stateFromSession(connection.session(), conletId); + if (model.isEmpty() || !model.get().vmName().equals(vmName)) { + continue; + } + if (event.type() == K8sObserver.ResponseType.DELETED) { + connection.respond( + new DeleteConlet(conletId, Collections.emptySet())); + } else { + updateVmDef(connection, model.get()); + } + } + } + } + + @Override + @SuppressWarnings({ "PMD.AvoidDecimalLiteralsInBigDecimalConstructor", + "PMD.ConfusingArgumentToVarargsMethod" }) + protected void doUpdateConletState(NotifyConletModel event, + ConsoleConnection channel, ViewerModel model) + throws Exception { + event.stop(); + var vmName = event.params().asString(0); + var vmChannel = channelManager.channel(vmName).orElse(null); + if (vmChannel == null) { + return; + } + switch (event.method()) { + case "selectedVm": + model.setVmName(event.params().asString(0)); + String jsonState = objectMapper.writeValueAsString(model); + channel.respond(new KeyValueStoreUpdate().update(storagePath( + channel.session(), model.getConletId()), jsonState)); + updateConfig(channel, model); + break; + case "start": + fire(new ModifyVm(vmName, "state", "Running", vmChannel)); + break; + case "stop": + fire(new ModifyVm(vmName, "state", "Stopped", vmChannel)); + break; + case "openConsole": + channelManager.channel(vmName).ifPresent( + vc -> fire(Event.onCompletion(new GetDisplayPassword(vmName), + ds -> openConsole(vmName, channel, model, ds)), vc)); + break; + default:// ignore + break; + } + } + + private void openConsole(String vmName, ConsoleConnection connection, + ViewerModel model, GetDisplayPassword pwQuery) { + var vmDef = channelManager.associated(vmName).orElse(null); + if (vmDef == null) { + return; + } + var addr = displayIp(vmDef); + if (addr.isEmpty()) { + logger.severe(() -> "Failed to find display IP for " + vmName); + return; + } + var port = GsonPtr.to(vmDef.data()).get(JsonPrimitive.class, "spec", + "vm", "display", "spice", "port"); + if (port.isEmpty()) { + logger.severe(() -> "No port defined for display of " + vmName); + return; + } + var proxyUrl = GsonPtr.to(vmDef.data()).get(JsonPrimitive.class, "spec", + "vm", "display", "spice", "proxyUrl"); + StringBuffer data = new StringBuffer(100) + .append("[virt-viewer]\ntype=spice\nhost=") + .append(addr.get().getHostAddress()).append("\nport=") + .append(Integer.toString(port.get().getAsInt())).append('\n'); + pwQuery.password().ifPresent(p -> { + data.append("password=").append(p).append('\n'); + }); + proxyUrl.map(JsonPrimitive::getAsString).ifPresent(u -> { + if (!Strings.isNullOrEmpty(u)) { + data.append("proxy=").append(u).append('\n'); + } + }); + connection.respond(new NotifyConletView(type(), + model.getConletId(), "openConsole", "application/x-virt-viewer", + Base64.getEncoder().encodeToString(data.toString().getBytes()))); + } + + private Optional displayIp(K8sDynamicModel vmDef) { + var server = GsonPtr.to(vmDef.data()).get(JsonPrimitive.class, "spec", + "vm", "display", "spice", "server"); + if (server.isPresent()) { + var srv = server.get().getAsString(); + try { + var addr = InetAddress.getByName(srv); + logger.fine(() -> "Using IP address from CRD for " + + vmDef.getMetadata().getName() + ": " + addr); + return Optional.of(addr); + } catch (UnknownHostException e) { + logger.log(Level.SEVERE, e, () -> "Invalid server address " + + srv + ": " + e.getMessage()); + return Optional.empty(); + } + } + var addrs = GsonPtr.to(vmDef.data()).getAsListOf(JsonPrimitive.class, + "nodeAddresses").stream().map(JsonPrimitive::getAsString) + .map(a -> { + try { + return InetAddress.getByName(a); + } catch (UnknownHostException e) { + logger.warning(() -> "Invalid IP address: " + a); + return null; + } + }).filter(a -> a != null).toList(); + logger.fine(() -> "Known IP addresses for " + + vmDef.getMetadata().getName() + ": " + addrs); + return addrs.stream() + .filter(a -> preferredIpVersion.isAssignableFrom(a.getClass())) + .findFirst().or(() -> addrs.stream().findFirst()); + } + + @Override + protected boolean doSetLocale(SetLocale event, ConsoleConnection channel, + String conletId) throws Exception { + return true; + } + + /** + * The Class VmsModel. + */ + public static class ViewerModel extends ConletBaseModel { + + private String vmName; + + /** + * Instantiates a new vms model. + * + * @param conletId the conlet id + */ + public ViewerModel(@JsonProperty("conletId") String conletId) { + super(conletId); + } + + /** + * Gets the vm name. + * + * @return the vmName + */ + @JsonGetter("vmName") + public String vmName() { + return vmName; + } + + /** + * Sets the vm name. + * + * @param vmName the vmName to set + */ + public void setVmName(String vmName) { + this.vmName = vmName; + } + + } +} diff --git a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewerFactory.java b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewerFactory.java new file mode 100644 index 0000000..6748f47 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewerFactory.java @@ -0,0 +1,54 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.vmviewer; + +import java.util.Map; +import java.util.Optional; +import org.jgrapes.core.Channel; +import org.jgrapes.core.ComponentType; +import org.jgrapes.webconsole.base.ConletComponentFactory; + +/** + * The factory service for {@link VmViewer}s. + */ +public class VmViewerFactory implements ConletComponentFactory { + + /* + * (non-Javadoc) + * + * @see org.jgrapes.core.ComponentFactory#componentType() + */ + @Override + public Class componentType() { + return VmViewer.class; + } + + /* + * (non-Javadoc) + * + * @see org.jgrapes.core.ComponentFactory#create(org.jgrapes.core.Channel, + * java.util.Map) + */ + @Override + public Optional create(Channel componentChannel, + Map properties) { + return Optional.of(new VmViewer(componentChannel)); + } + +} diff --git a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-functions.ts b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-functions.ts new file mode 100644 index 0000000..2c66aeb --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-functions.ts @@ -0,0 +1,206 @@ +/* + * 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 . + */ + +import { + reactive, ref, createApp, computed, watch +} from "vue"; +import JGConsole from "jgconsole"; +import JgwcPlugin, { JGWC } from "jgwc"; +import { provideApi, getApi } from "aash-plugin"; +import l10nBundles from "l10nBundles"; + +import "./VmViewer-style.scss"; + +// For global access +declare global { + interface Window { + orgJDrupesVmOperatorVmViewer: { + initPreview?: (previewDom: HTMLElement, isUpdate: boolean) => void, + initEdit?: (viewDom: HTMLElement, isUpdate: boolean) => void + applyEdit?: (viewDom: HTMLElement, apply: boolean) => void + } + } +} + +window.orgJDrupesVmOperatorVmViewer = {}; + +interface Api { + /* eslint-disable @typescript-eslint/no-explicit-any */ + vmName: string; + vmDefinition: any; +} + +const localize = (key: string) => { + return JGConsole.localize( + l10nBundles, JGWC.lang(), key); +}; + +window.orgJDrupesVmOperatorVmViewer.initPreview = (previewDom: HTMLElement, + _isUpdate: boolean) => { + const app = createApp({ + setup(_props: object) { + const conletId = (previewDom.closest( + "[data-conlet-id]")!).dataset["conletId"]!; + const resourceBase = (previewDom.closest( + "*[data-conlet-resource-base]")!).dataset.conletResourceBase; + + const previewApi: Api = reactive({ + vmName: "", + vmDefinition: {} + }); + const vmDef = computed(() => previewApi.vmDefinition); + + watch(() => previewApi.vmName, (name: string) => { + if (name !== "") { + JGConsole.instance.updateConletTitle(conletId, name); + } + }); + + provideApi(previewDom, previewApi); + + const vmAction = (vmName: string, action: string) => { + JGConsole.notifyConletModel(conletId, action, vmName); + }; + + return { localize, resourceBase, vmDef, vmAction }; + }, + template: ` +
{{ localize("maximumCpus") }}{{ entry.spec.vm.maximumCpus }}{{ maximumCpus(entry) }}
{{ localize("requestedCpus") }}{{ vmSummary.runningVms }} / {{ vmSummary.totalVms }}
{{ localize("Used CPUs") }}:{{ localize("currentCpus") }}: {{ vmSummary.usedCpus }}
{{ localize("Used RAM") }}:{{ localize("currentRam") }}: {{ formatMemory(Number(vmSummary.usedRam)) }}
+ + + + + + + +
+ + + + + +
` + }); + app.use(JgwcPlugin, []); + app.config.globalProperties.window = window; + app.mount(previewDom); +}; + +JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmviewer.VmViewer", + "updateConfig", function(conletId: string, vmName: string) { + const conlet = JGConsole.findConletPreview(conletId); + if (!conlet) { + return; + } + const api = getApi(conlet.element().querySelector( + ":scope .jdrupes-vmoperator-vmviewer-preview"))!; + api.vmName = vmName; + }); + +JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmviewer.VmViewer", + "updateVmDefinition", function(conletId: string, vmDefinition: any) { + const conlet = JGConsole.findConletPreview(conletId); + if (!conlet) { + return; + } + const api = getApi(conlet.element().querySelector( + ":scope .jdrupes-vmoperator-vmviewer-preview"))!; + // Add some short-cuts for rendering + vmDefinition.name = vmDefinition.metadata.name; + vmDefinition.currentCpus = vmDefinition.status.cpus; + vmDefinition.currentRam = Number(vmDefinition.status.ram); + for (const condition of vmDefinition.status.conditions) { + if (condition.type === "Running") { + vmDefinition.running = condition.status === "True"; + vmDefinition.runningConditionSince + = new Date(condition.lastTransitionTime); + break; + } + } + api.vmDefinition = vmDefinition; + }); + +JGConsole.registerConletFunction("org.jdrupes.vmoperator.vmviewer.VmViewer", + "openConsole", function(_conletId: string, mimeType: string, data: string) { + let target = document.getElementById( + "org.jdrupes.vmoperator.vmviewer.VmViewer.target"); + if (!target) { + target = document.createElement("iframe"); + target.id = "org.jdrupes.vmoperator.vmviewer.VmViewer.target"; + target.setAttribute("name", target.id); + target.setAttribute("style", "display: none;"); + document.querySelector("body")!.append(target); + } + const url = "data:" + mimeType + ";base64," + data; + window.open(url, target.id); + }); + +window.orgJDrupesVmOperatorVmViewer.initEdit = (dialogDom: HTMLElement, + isUpdate: boolean) => { + if (isUpdate) { + return; + } + const app = createApp({ + setup() { + const formId = (dialogDom + .closest("*[data-conlet-id]")!).id + "-form"; + + const localize = (key: string) => { + return JGConsole.localize( + l10nBundles, JGWC.lang()!, key); + }; + + const vmNameInput = ref(""); + const conletId = (dialogDom.closest( + "[data-conlet-id]")!).dataset["conletId"]!; + const conlet = JGConsole.findConletPreview(conletId); + if (conlet) { + const api = getApi(conlet.element().querySelector( + ":scope .jdrupes-vmoperator-vmviewer-preview"))!; + vmNameInput.value = api.vmName; + } + + provideApi(dialogDom, vmNameInput); + + return { formId, localize, vmNameInput }; + } + }); + app.use(JgwcPlugin); + app.mount(dialogDom); +} + +window.orgJDrupesVmOperatorVmViewer.applyEdit = + (dialogDom: HTMLElement, apply: boolean) => { + if (!apply) { + return; + } + const conletId = (dialogDom.closest("[data-conlet-id]")!) + .dataset["conletId"]!; + const vmName = getApi>(dialogDom!)!.value; + JGConsole.notifyConletModel(conletId, "selectedVm", vmName); +} diff --git a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-style.scss b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-style.scss new file mode 100644 index 0000000..8508ba5 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-style.scss @@ -0,0 +1,42 @@ +/* + * 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 . + */ + +/* + * Conlet specific styles. + */ +.jdrupes-vmoperator-vmviewer-preview img { + height: 3em; + padding: 0.25rem; + + &:hover { + box-shadow: var(--darkening); + } +} + +.jdrupes-vmoperator-vmviewer-preview-action-list { + white-space: nowrap; + + [role=button] { + padding: 0.25rem; + + &:not([aria-disabled]):hover { + box-shadow: var(--darkening); + } + } +} + diff --git a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/l10nBundles-stub.d.ts b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/l10nBundles-stub.d.ts new file mode 100644 index 0000000..8ca03f3 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/l10nBundles-stub.d.ts @@ -0,0 +1 @@ +export default new Map>(); diff --git a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/package-info.java b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/package-info.java new file mode 100644 index 0000000..9a4045a --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/package-info.java @@ -0,0 +1,19 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.vmviewer; \ No newline at end of file diff --git a/org.jdrupes.vmoperator.vmviewer/tsconfig.json b/org.jdrupes.vmoperator.vmviewer/tsconfig.json new file mode 100644 index 0000000..6418f59 --- /dev/null +++ b/org.jdrupes.vmoperator.vmviewer/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "es2015", + "module": "es2015", + "sourceMap": true, + "inlineSources": true, + "declaration": true, + "importHelpers": true, + "strict": true, + "moduleResolution": "node", + "experimentalDecorators": true, + "lib": ["DOM", "ES2020"], + "paths": { + "aash-plugin": ["./build/unpacked/org/jgrapes/webconsole/provider/jgwcvuecomponents/aash-vue-components/lib/AashPlugin"], + "jgconsole": ["./build/unpacked/org/jgrapes/webconsole/base/JGConsole"], + "jgwc": ["./build/unpacked/org/jgrapes/webconsole/provider/jgwcvuecomponents/jgwc-vue-components/jgwc-components"], + "l10nBundles": ["./src/org/jdrupes/vmoperator/vmviewer/browser/l10nBundles-stub"], + "vue": ["./build/unpacked/org/jgrapes/webconsole/provider/vue/vue/vue"] + } + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "l10nBundles-stub.ts"] +} diff --git a/settings.gradle b/settings.gradle index cf075af..64f3056 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,6 +13,7 @@ rootProject.name = 'VM-Operator' include 'org.jdrupes.vmoperator.manager' include 'org.jdrupes.vmoperator.manager.events' include 'org.jdrupes.vmoperator.vmconlet' +include 'org.jdrupes.vmoperator.vmviewer' include 'org.jdrupes.vmoperator.runner.qemu' include 'org.jdrupes.vmoperator.common' include 'org.jdrupes.vmoperator.util' From 659463b3b46c5bd41bc2ba56ccdc8fe14087d6c5 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Sat, 1 Jun 2024 11:12:15 +0200 Subject: [PATCH 140/462] Viewer ACL (#26) Provide ACLs (together with general improvements) for the viewer conlet. --- deploy/crds/vms-crd.yaml | 27 ++ dev-example/config.yaml | 7 +- dev-example/kustomization.yaml | 6 + dev-example/test-vm.yaml | 10 + .../common/DynamicTypeAdapterFactory.java | 197 ++++++++++++ .../K8sDynamicModelTypeAdapterFactory.java | 131 -------- .../vmoperator/common/K8sDynamicModels.java | 123 +------- .../common/K8sDynamicModelsBase.java | 174 +++++++++++ .../vmoperator/common/K8sDynamicStub.java | 58 ++-- .../vmoperator/common/K8sDynamicStubBase.java | 51 ++++ .../vmoperator/common/K8sGenericStub.java | 123 ++++---- .../vmoperator/common/K8sV1PodStub.java | 15 +- .../vmoperator/common/K8sV1SecretStub.java | 29 +- .../vmoperator/common/K8sV1ServiceStub.java | 16 +- .../vmoperator/common/VmDefinitionModel.java | 123 ++++++++ .../vmoperator/common/VmDefinitionModels.java | 39 +++ .../vmoperator/common/VmDefinitionStub.java | 157 ++++++++++ .../events/DisplayPasswordChanged.java | 76 ----- .../manager/events/GetDisplayPassword.java | 24 +- .../vmoperator/manager/events/VmChannel.java | 8 +- .../manager/events/VmDefChanged.java | 8 +- org.jdrupes.vmoperator.manager/build.gradle | 1 + .../manager/ManagerIntro-Preview.md | 5 + .../manager/ManagerIntro-Preview_de.md | 6 + .../vmoperator/manager/l10n.properties | 1 + .../vmoperator/manager/l10n_de.properties | 19 ++ .../vmoperator/manager/AvoidEmptyPolicy.java | 51 +++- .../manager/ConfigMapReconciler.java | 4 +- .../jdrupes/vmoperator/manager/Constants.java | 6 + .../vmoperator/manager/Controller.java | 2 +- .../manager/DisplayPasswordMonitor.java | 102 ------- .../manager/DisplaySecretMonitor.java | 285 ++++++++++++++++++ .../manager/DisplaySecretReconciler.java | 106 +++++++ .../vmoperator/manager/Reconciler.java | 3 + .../jdrupes/vmoperator/manager/VmMonitor.java | 18 +- .../vmoperator/runner/qemu/StatusUpdater.java | 9 +- .../jdrupes/vmoperator/vmconlet/VmConlet.java | 10 +- .../vmconlet/browser/VmConlet-style.scss | 2 +- .../vmoperator/vmviewer/l10n_de.properties | 5 +- .../jdrupes/vmoperator/vmviewer/VmViewer.java | 264 +++++++++++++--- .../vmviewer/browser/VmViewer-functions.ts | 11 +- .../vmviewer/browser/VmViewer-style.scss | 31 +- 42 files changed, 1664 insertions(+), 679 deletions(-) create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/DynamicTypeAdapterFactory.java delete mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelTypeAdapterFactory.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelsBase.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStubBase.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionModel.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionModels.java create mode 100644 org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionStub.java delete mode 100644 org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/DisplayPasswordChanged.java create mode 100644 org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview.md create mode 100644 org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview_de.md create mode 100644 org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/l10n_de.properties delete mode 100644 org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplayPasswordMonitor.java create mode 100644 org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretMonitor.java create mode 100644 org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretReconciler.java diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index de65f8a..f441cbc 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -990,6 +990,30 @@ spec: description: Copied to cloud-init's network-config file. type: object x-kubernetes-preserve-unknown-fields: true + permissions: + type: array + description: >- + Defines permissions for accessing and manipulating the VM. + items: + type: object + description: >- + Permissions can be granted to a user or to a role. + oneOf: + - required: + - user + - required: + - role + properties: + user: + type: string + role: + type: string + may: + type: array + items: + type: string + enum: ["start", "stop", "accessConsole", "*"] + default: [] vm: type: object description: Defines the VM. @@ -1395,6 +1419,9 @@ spec: to the spice server. Defaults to the address of the node that the VM is running on. type: string + generateSecret: + type: boolean + default: true proxyUrl: description: >- If specified, is copied to the generated diff --git a/dev-example/config.yaml b/dev-example/config.yaml index 3c94254..579103d 100644 --- a/dev-example/config.yaml +++ b/dev-example/config.yaml @@ -28,6 +28,8 @@ # User admin has role admin admin: - admin + test: + - user # All users have role other "*": - other @@ -37,11 +39,14 @@ # Admins can use all conlets admin: - "*" + user: + - org.jdrupes.vmoperator.vmviewer.VmViewer # Others cannot use any conlet (except login conlet to log out) other: - - --org.jdrupes.vmoperator.vmconlet.VmConlet - org.jgrapes.webconlet.oidclogin.LoginConlet "/ComponentCollector": "/VmViewer": displayResource: preferredIpVersion: ipv4 + syncPreviewsFor: + - role: user diff --git a/dev-example/kustomization.yaml b/dev-example/kustomization.yaml index 9829bf6..19b6295 100644 --- a/dev-example/kustomization.yaml +++ b/dev-example/kustomization.yaml @@ -54,6 +54,8 @@ patches: # User admin has role admin admin: - admin + test: + - user # All users have role other "*": - other @@ -63,6 +65,8 @@ patches: # Admins can use all conlets admin: - "*" + user: + - org.jdrupes.vmoperator.vmviewer.VmViewer # Others cannot use any conlet (except login conlet to log out) other: - org.jgrapes.webconlet.locallogin.LoginConlet @@ -70,6 +74,8 @@ patches: "/VmViewer": displayResource: preferredIpVersion: ipv4 + syncPreviewsFor: + - role: user - target: group: apps version: v1 diff --git a/dev-example/test-vm.yaml b/dev-example/test-vm.yaml index 19144d5..e874ef8 100644 --- a/dev-example/test-vm.yaml +++ b/dev-example/test-vm.yaml @@ -7,8 +7,17 @@ spec: image: repository: docker-registry.lan.mnl.de path: vmoperator/org.jdrupes.vmoperator.runner.qemu-alpine + version: latest pullPolicy: Always + permissions: + - user: admin + may: + - "*" + - user: test + may: + - "accessConsole" + resources: requests: cpu: 1 @@ -52,3 +61,4 @@ spec: display: spice: port: 5810 + generateSecret: true diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/DynamicTypeAdapterFactory.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/DynamicTypeAdapterFactory.java new file mode 100644 index 0000000..d21eed4 --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/DynamicTypeAdapterFactory.java @@ -0,0 +1,197 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import com.google.gson.Gson; +import com.google.gson.InstanceCreator; +import com.google.gson.JsonObject; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.kubernetes.client.openapi.ApiClient; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Type; + +/** + * A factory for creating objects. + * + * @param the generic type + * @param the generic type + */ +public class DynamicTypeAdapterFactory> implements TypeAdapterFactory { + + private final Class objectClass; + private final Class objectListClass; + + /** + * Make sure that this adapter is registered. + * + * @param client the client + */ + public void register(ApiClient client) { + if (!ModelCreator.class + .equals(client.getJSON().getGson().getAdapter(objectClass) + .getClass()) + || !ModelsCreator.class.equals(client.getJSON().getGson() + .getAdapter(objectListClass).getClass())) { + Gson gson = client.getJSON().getGson(); + client.getJSON().setGson(gson.newBuilder() + .registerTypeAdapterFactory(this).create()); + } + } + + /** + * Instantiates a new generic type adapter factory. + * + * @param objectClass the object class + * @param objectListClass the object list class + */ + public DynamicTypeAdapterFactory(Class objectClass, + Class objectListClass) { + this.objectClass = objectClass; + this.objectListClass = objectListClass; + } + + /** + * Creates a type adapter for the given type. + * + * @param the generic type + * @param gson the gson + * @param typeToken the type token + * @return the type adapter or null if the type is not handles by + * this factory + */ + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken typeToken) { + if (TypeToken.get(objectClass).equals(typeToken)) { + return (TypeAdapter) new ModelCreator(gson); + } + if (TypeToken.get(objectListClass).equals(typeToken)) { + return (TypeAdapter) new ModelsCreator(gson); + } + return null; + } + + /** + * The Class ModelCreator. + */ + private class ModelCreator extends TypeAdapter + implements InstanceCreator { + private final Gson delegate; + + /** + * Instantiates a new object state creator. + * + * @param delegate the delegate + */ + public ModelCreator(Gson delegate) { + this.delegate = delegate; + } + + @Override + public O createInstance(Type type) { + try { + return objectClass.getConstructor(Gson.class, JsonObject.class) + .newInstance(delegate, null); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + return null; + } + } + + @Override + public void write(JsonWriter jsonWriter, O state) + throws IOException { + jsonWriter.jsonValue(delegate.toJson(state.data())); + } + + @Override + public O read(JsonReader jsonReader) + throws IOException { + try { + return objectClass.getConstructor(Gson.class, JsonObject.class) + .newInstance(delegate, + delegate.fromJson(jsonReader, JsonObject.class)); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + return null; + } + } + } + + /** + * The Class ModelsCreator. + */ + private class ModelsCreator extends TypeAdapter + implements InstanceCreator { + + private final Gson delegate; + + /** + * Instantiates a new object states creator. + * + * @param delegate the delegate + */ + public ModelsCreator(Gson delegate) { + this.delegate = delegate; + } + + @Override + public L createInstance(Type type) { + try { + return objectListClass + .getConstructor(Gson.class, JsonObject.class) + .newInstance(delegate, null); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + return null; + } + } + + @Override + public void write(JsonWriter jsonWriter, L states) + throws IOException { + jsonWriter.jsonValue(delegate.toJson(states.data())); + } + + @Override + public L read(JsonReader jsonReader) + throws IOException { + try { + return objectListClass + .getConstructor(Gson.class, JsonObject.class) + .newInstance(delegate, + delegate.fromJson(jsonReader, JsonObject.class)); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + return null; + } + } + } + +} diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelTypeAdapterFactory.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelTypeAdapterFactory.java deleted file mode 100644 index 33a8e18..0000000 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelTypeAdapterFactory.java +++ /dev/null @@ -1,131 +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 . - */ - -package org.jdrupes.vmoperator.common; - -import com.google.gson.Gson; -import com.google.gson.InstanceCreator; -import com.google.gson.JsonObject; -import com.google.gson.TypeAdapter; -import com.google.gson.TypeAdapterFactory; -import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; -import java.io.IOException; -import java.lang.reflect.Type; - -/** - * A factory for creating K8sDynamicModel(s) objects. - */ -public class K8sDynamicModelTypeAdapterFactory implements TypeAdapterFactory { - - /** - * Creates a type adapter for the given type. - * - * @param the generic type - * @param gson the gson - * @param typeToken the type token - * @return the type adapter or null if the type is not handles by - * this factory - */ - @SuppressWarnings("unchecked") - @Override - public TypeAdapter create(Gson gson, TypeToken typeToken) { - if (TypeToken.get(K8sDynamicModel.class).equals(typeToken)) { - return (TypeAdapter) new K8sDynamicModelCreator(gson); - } - if (TypeToken.get(K8sDynamicModels.class).equals(typeToken)) { - return (TypeAdapter) new K8sDynamicModelsCreator(gson); - } - return null; - } - - /** - * The Class K8sDynamicModelCreator. - */ - /* default */ class K8sDynamicModelCreator - extends TypeAdapter - implements InstanceCreator { - private final Gson delegate; - - /** - * Instantiates a new object state creator. - * - * @param delegate the delegate - */ - public K8sDynamicModelCreator(Gson delegate) { - this.delegate = delegate; - } - - @Override - public K8sDynamicModel createInstance(Type type) { - return new K8sDynamicModel(delegate, null); - } - - @Override - public void write(JsonWriter jsonWriter, K8sDynamicModel state) - throws IOException { - jsonWriter.jsonValue(delegate.toJson(state.data())); - } - - @Override - public K8sDynamicModel read(JsonReader jsonReader) - throws IOException { - return new K8sDynamicModel(delegate, - delegate.fromJson(jsonReader, JsonObject.class)); - } - } - - /** - * The Class K8sDynamicModelsCreator. - */ - /* default */class K8sDynamicModelsCreator - extends TypeAdapter - implements InstanceCreator { - - private final Gson delegate; - - /** - * Instantiates a new object states creator. - * - * @param delegate the delegate - */ - public K8sDynamicModelsCreator(Gson delegate) { - this.delegate = delegate; - } - - @Override - public K8sDynamicModels createInstance(Type type) { - return new K8sDynamicModels(delegate, null); - } - - @Override - public void write(JsonWriter jsonWriter, K8sDynamicModels states) - throws IOException { - jsonWriter.jsonValue(delegate.toJson(states.data())); - } - - @Override - public K8sDynamicModels read(JsonReader jsonReader) - throws IOException { - return new K8sDynamicModels(delegate, - delegate.fromJson(jsonReader, JsonObject.class)); - } - } - -} diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModels.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModels.java index 165b10e..d165c10 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModels.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModels.java @@ -19,14 +19,8 @@ package org.jdrupes.vmoperator.common; import com.google.gson.Gson; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; import io.kubernetes.client.common.KubernetesListObject; -import io.kubernetes.client.openapi.Configuration; -import io.kubernetes.client.openapi.models.V1ListMeta; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; /** * Represents a list of Kubernetes objects each of which is @@ -35,11 +29,7 @@ import java.util.Objects; * notably the metadata, is made available through the methods * defined by {@link KubernetesListObject}. */ -public class K8sDynamicModels implements KubernetesListObject { - - private final JsonObject data; - private final V1ListMeta metadata; - private final List items; +public class K8sDynamicModels extends K8sDynamicModelsBase { /** * Initialize the object list using the given JSON data. @@ -48,116 +38,7 @@ public class K8sDynamicModels implements KubernetesListObject { * @param data the data */ public K8sDynamicModels(Gson delegate, JsonObject data) { - this.data = data; - metadata = delegate.fromJson(data.get("metadata"), V1ListMeta.class); - items = new ArrayList<>(); - for (JsonElement e : data.get("items").getAsJsonArray()) { - items.add(new K8sDynamicModel(delegate, e.getAsJsonObject())); - } + super(K8sDynamicModel.class, delegate, data); } - @Override - public String getApiVersion() { - return apiVersion(); - } - - /** - * Gets the API version. (Abbreviated method name for convenience.) - * - * @return the API version - */ - public String apiVersion() { - return data.get("apiVersion").getAsString(); - } - - @Override - public String getKind() { - return kind(); - } - - /** - * Gets the kind. (Abbreviated method name for convenience.) - * - * @return the kind - */ - public String kind() { - return data.get("kind").getAsString(); - } - - @Override - public V1ListMeta getMetadata() { - return metadata; - } - - /** - * Gets the metadata. (Abbreviated method name for convenience.) - * - * @return the metadata - */ - public V1ListMeta metadata() { - return metadata; - } - - /** - * Returns the JSON representation of this object. - * - * @return the JOSN representation - */ - public JsonObject data() { - return data; - } - - @Override - public List getItems() { - return items; - } - - /** - * Sets the api version. - * - * @param apiVersion the new api version - */ - public void setApiVersion(String apiVersion) { - data.addProperty("apiVersion", apiVersion); - } - - /** - * Sets the kind. - * - * @param kind the new kind - */ - public void setKind(String kind) { - data.addProperty("kind", kind); - } - - /** - * Sets the metadata. - * - * @param objectMeta the new metadata - */ - public void setMetadata(V1ListMeta objectMeta) { - data.add("metadata", - Configuration.getDefaultApiClient().getJSON().getGson() - .toJsonTree(objectMeta)); - } - - @Override - public int hashCode() { - return Objects.hash(data); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - K8sDynamicModels other = (K8sDynamicModels) obj; - return Objects.equals(data, other.data); - } } diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelsBase.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelsBase.java new file mode 100644 index 0000000..4e21c0e --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicModelsBase.java @@ -0,0 +1,174 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import io.kubernetes.client.common.KubernetesListObject; +import io.kubernetes.client.openapi.Configuration; +import io.kubernetes.client.openapi.models.V1ListMeta; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Represents a list of Kubernetes objects each of which is + * represented using a JSON data structure. + * Some information that is common to all Kubernetes objects, + * notably the metadata, is made available through the methods + * defined by {@link KubernetesListObject}. + */ +public class K8sDynamicModelsBase + implements KubernetesListObject { + + private final JsonObject data; + private final V1ListMeta metadata; + private final List items; + + /** + * Initialize the object list using the given JSON data. + * + * @param itemClass the item class + * @param delegate the gson instance to use for extracting structured data + * @param data the data + */ + public K8sDynamicModelsBase(Class itemClass, Gson delegate, + JsonObject data) { + this.data = data; + metadata = delegate.fromJson(data.get("metadata"), V1ListMeta.class); + items = new ArrayList<>(); + for (JsonElement e : data.get("items").getAsJsonArray()) { + try { + items.add(itemClass.getConstructor(Gson.class, JsonObject.class) + .newInstance(delegate, e.getAsJsonObject())); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException exc) { + throw new IllegalArgumentException(exc); + } + } + } + + @Override + public String getApiVersion() { + return apiVersion(); + } + + /** + * Gets the API version. (Abbreviated method name for convenience.) + * + * @return the API version + */ + public String apiVersion() { + return data.get("apiVersion").getAsString(); + } + + @Override + public String getKind() { + return kind(); + } + + /** + * Gets the kind. (Abbreviated method name for convenience.) + * + * @return the kind + */ + public String kind() { + return data.get("kind").getAsString(); + } + + @Override + public V1ListMeta getMetadata() { + return metadata; + } + + /** + * Gets the metadata. (Abbreviated method name for convenience.) + * + * @return the metadata + */ + public V1ListMeta metadata() { + return metadata; + } + + /** + * Returns the JSON representation of this object. + * + * @return the JOSN representation + */ + public JsonObject data() { + return data; + } + + @Override + public List getItems() { + return items; + } + + /** + * Sets the api version. + * + * @param apiVersion the new api version + */ + public void setApiVersion(String apiVersion) { + data.addProperty("apiVersion", apiVersion); + } + + /** + * Sets the kind. + * + * @param kind the new kind + */ + public void setKind(String kind) { + data.addProperty("kind", kind); + } + + /** + * Sets the metadata. + * + * @param objectMeta the new metadata + */ + public void setMetadata(V1ListMeta objectMeta) { + data.add("metadata", + Configuration.getDefaultApiClient().getJSON().getGson() + .toJsonTree(objectMeta)); + } + + @Override + public int hashCode() { + return Objects.hash(data); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + K8sDynamicModelsBase other = (K8sDynamicModelsBase) obj; + return Objects.equals(data, other.data); + } +} diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStub.java index e6d36c5..afed802 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStub.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStub.java @@ -18,10 +18,8 @@ package org.jdrupes.vmoperator.common; -import com.google.gson.Gson; import io.kubernetes.client.Discovery.APIResource; import io.kubernetes.client.apimachinery.GroupVersionKind; -import io.kubernetes.client.openapi.ApiClient; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.util.generic.options.ListOptions; import java.io.Reader; @@ -35,40 +33,23 @@ import java.util.Collection; */ @SuppressWarnings("PMD.DataflowAnomalyAnalysis") public class K8sDynamicStub - extends K8sGenericStub { + extends K8sDynamicStubBase { + + private static DynamicTypeAdapterFactory taf = new K8sDynamicModelTypeAdapterFactory(); /** * Instantiates a new dynamic stub. * - * @param objectClass the object class - * @param objectListClass the object list class * @param client the client * @param context the context * @param namespace the namespace * @param name the name */ - public K8sDynamicStub(Class objectClass, - Class objectListClass, K8sClient client, + public K8sDynamicStub(K8sClient client, APIResource context, String namespace, String name) { - super(objectClass, objectListClass, client, context, namespace, name); - - // Make sure that we have an adapter for our type - Gson gson = client.getJSON().getGson(); - if (!checkAdapters(client)) { - client.getJSON().setGson(gson.newBuilder() - .registerTypeAdapterFactory( - new K8sDynamicModelTypeAdapterFactory()) - .create()); - } - } - - private boolean checkAdapters(ApiClient client) { - return K8sDynamicModelTypeAdapterFactory.K8sDynamicModelCreator.class - .equals(client.getJSON().getGson().getAdapter(K8sDynamicModel.class) - .getClass()) - && K8sDynamicModelTypeAdapterFactory.K8sDynamicModelsCreator.class - .equals(client.getJSON().getGson() - .getAdapter(K8sDynamicModels.class).getClass()); + super(K8sDynamicModel.class, K8sDynamicModels.class, taf, client, + context, namespace, name); } /** @@ -88,8 +69,8 @@ public class K8sDynamicStub public static K8sDynamicStub get(K8sClient client, GroupVersionKind gvk, String namespace, String name) throws ApiException { - return K8sGenericStub.get(K8sDynamicModel.class, K8sDynamicModels.class, - client, gvk, namespace, name, K8sDynamicStub::new); + return new K8sDynamicStub(client, apiResource(client, gvk), namespace, + name); } /** @@ -106,8 +87,7 @@ public class K8sDynamicStub "PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" }) public static K8sDynamicStub get(K8sClient client, APIResource context, String namespace, String name) { - return K8sGenericStub.get(K8sDynamicModel.class, K8sDynamicModels.class, - client, context, namespace, name, K8sDynamicStub::new); + return new K8sDynamicStub(client, context, namespace, name); } /** @@ -125,7 +105,7 @@ public class K8sDynamicStub K8s.yamlToJson(client, yaml)); return K8sGenericStub.create(K8sDynamicModel.class, K8sDynamicModels.class, client, context, model, - K8sDynamicStub::new); + (c, ns, n) -> new K8sDynamicStub(c, context, ns, n)); } /** @@ -143,7 +123,7 @@ public class K8sDynamicStub throws ApiException { return K8sGenericStub.list(K8sDynamicModel.class, K8sDynamicModels.class, client, context, namespace, options, - K8sDynamicStub::new); + (c, ns, n) -> new K8sDynamicStub(c, context, ns, n)); } /** @@ -160,4 +140,18 @@ public class K8sDynamicStub return list(client, context, namespace, new ListOptions()); } + /** + * A factory for creating K8sDynamicModel(s) objects. + */ + public static class K8sDynamicModelTypeAdapterFactory extends + DynamicTypeAdapterFactory { + + /** + * Instantiates a new dynamic model type adapter factory. + */ + public K8sDynamicModelTypeAdapterFactory() { + super(K8sDynamicModel.class, K8sDynamicModels.class); + } + } + } \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStubBase.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStubBase.java new file mode 100644 index 0000000..44f419c --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sDynamicStubBase.java @@ -0,0 +1,51 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import io.kubernetes.client.Discovery.APIResource; + +/** + * A stub for namespaced custom objects. It uses a dynamic model + * (see {@link K8sDynamicModel}) for representing the object's + * state and can therefore be used for any kind of object, especially + * custom objects. + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public abstract class K8sDynamicStubBase> extends K8sGenericStub { + + /** + * Instantiates a new dynamic stub. + * + * @param objectClass the object class + * @param objectListClass the object list class + * @param client the client + * @param context the context + * @param namespace the namespace + * @param name the name + */ + @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") + public K8sDynamicStubBase(Class objectClass, + Class objectListClass, DynamicTypeAdapterFactory taf, + K8sClient client, APIResource context, String namespace, + String name) { + super(objectClass, objectListClass, client, context, namespace, name); + taf.register(client); + } +} \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sGenericStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sGenericStub.java index db68a38..f118a17 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sGenericStub.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sGenericStub.java @@ -26,9 +26,11 @@ import io.kubernetes.client.custom.V1Patch; import io.kubernetes.client.openapi.ApiException; import io.kubernetes.client.util.Strings; import io.kubernetes.client.util.generic.GenericKubernetesApi; +import io.kubernetes.client.util.generic.KubernetesApiResponse; import io.kubernetes.client.util.generic.options.GetOptions; import io.kubernetes.client.util.generic.options.ListOptions; import io.kubernetes.client.util.generic.options.PatchOptions; +import io.kubernetes.client.util.generic.options.UpdateOptions; import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Collection; @@ -228,7 +230,8 @@ public class K8sGenericStub patch(String patchType, V1Patch patch, PatchOptions options) throws ApiException { return K8s - .optional(api.patch(namespace, name, patchType, patch, options)); + .optional(api.patch(namespace, name, patchType, patch, options) + .throwsApiException()); } /** @@ -245,6 +248,30 @@ public class K8sGenericStub update(O object) throws ApiException { + return api.update(object).throwsApiException(); + } + + /** + * Update the object. + * + * @param object the object + * @param options the options + * @return the kubernetes api response + * @throws ApiException the api exception + */ + public KubernetesApiResponse update(O object, UpdateOptions options) + throws ApiException { + return api.update(object, options).throwsApiException(); + } + /** * A supplier for generic stubs. * @@ -258,17 +285,13 @@ public class K8sGenericStub objectClass, Class objectListClass, K8sClient client, - APIResource context, String namespace, String name); + R get(K8sClient client, String namespace, String name); } @Override @@ -278,68 +301,6 @@ public class K8sGenericStub the object type - * @param the object list type - * @param the stub type - * @param objectClass the object class - * @param objectListClass the object list class - * @param client the client - * @param gvk the group, version and kind - * @param namespace the namespace - * @param name the name - * @param provider the provider - * @return the stub if the object exists - * @throws ApiException the api exception - */ - @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop" }) - public static > - R get(Class objectClass, Class objectListClass, - K8sClient client, GroupVersionKind gvk, String namespace, - String name, GenericSupplier provider) - throws ApiException { - var context = K8s.context(client, gvk.getGroup(), gvk.getVersion(), - gvk.getKind()); - if (context.isEmpty()) { - throw new ApiException("No known API for " + gvk.getGroup() - + "/" + gvk.getVersion() + " " + gvk.getKind()); - } - return provider.get(objectClass, objectListClass, client, context.get(), - namespace, name); - } - - /** - * Get a namespaced object stub. - * - * @param the object type - * @param the object list type - * @param the stub type - * @param objectClass the object class - * @param objectListClass the object list class - * @param client the client - * @param context the context - * @param namespace the namespace - * @param name the name - * @param provider the provider - * @return the stub if the object exists - * @throws ApiException the api exception - */ - @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", - "PMD.UseObjectForClearerAPI" }) - public static > - R get(Class objectClass, Class objectListClass, - K8sClient client, APIResource context, String namespace, - String name, GenericSupplier provider) { - return provider.get(objectClass, objectListClass, client, - context, namespace, name); - } - /** * Get a namespaced object stub for a newly created object. * @@ -366,8 +327,7 @@ public class K8sGenericStub { public static Collection list(K8sClient client, String namespace, ListOptions options) throws ApiException { return K8sGenericStub.list(V1Pod.class, V1PodList.class, client, - CONTEXT, namespace, options, K8sV1PodStub::getGeneric); + CONTEXT, namespace, options, (clnt, nscp, + name) -> new K8sV1PodStub(clnt, nscp, name)); } - - /** - * Provide {@link GenericSupplier}. - */ - @SuppressWarnings({ "PMD.UnusedFormalParameter", - "PMD.UnusedPrivateMethod" }) - private static K8sV1PodStub getGeneric(Class objectClass, - Class objectListClass, K8sClient client, - APIResource context, String namespace, String name) { - return new K8sV1PodStub(client, namespace, name); - } - } \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java index 9a43883..a847d36 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1SecretStub.java @@ -25,7 +25,6 @@ import io.kubernetes.client.openapi.models.V1SecretList; import io.kubernetes.client.util.generic.options.ListOptions; import java.util.Collection; import java.util.List; -import org.jdrupes.vmoperator.common.K8sGenericStub.GenericSupplier; /** * A stub for secrets (v1). @@ -62,6 +61,20 @@ public class K8sV1SecretStub extends K8sGenericStub { return new K8sV1SecretStub(client, namespace, name); } + /** + * Creates an object stub from a model. + * + * @param client the client + * @param model the model + * @return the k 8 s dynamic stub + * @throws ApiException the api exception + */ + public static K8sV1SecretStub create(K8sClient client, V1Secret model) + throws ApiException { + return K8sGenericStub.create(V1Secret.class, + V1SecretList.class, client, CONTEXT, model, K8sV1SecretStub::new); + } + /** * Get the stubs for the objects in the given namespace that match * the criteria from the given options. @@ -75,18 +88,6 @@ public class K8sV1SecretStub extends K8sGenericStub { public static Collection list(K8sClient client, String namespace, ListOptions options) throws ApiException { return K8sGenericStub.list(V1Secret.class, V1SecretList.class, client, - CONTEXT, namespace, options, K8sV1SecretStub::getGeneric); + CONTEXT, namespace, options, K8sV1SecretStub::new); } - - /** - * Provide {@link GenericSupplier}. - */ - @SuppressWarnings({ "PMD.UnusedFormalParameter", - "PMD.UnusedPrivateMethod" }) - private static K8sV1SecretStub getGeneric(Class objectClass, - Class objectListClass, K8sClient client, - APIResource context, String namespace, String name) { - return new K8sV1SecretStub(client, namespace, name); - } - } \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ServiceStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ServiceStub.java index 74f7f61..2157a1d 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ServiceStub.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/K8sV1ServiceStub.java @@ -25,7 +25,6 @@ import io.kubernetes.client.openapi.models.V1ServiceList; import io.kubernetes.client.util.generic.options.ListOptions; import java.util.Collection; import java.util.List; -import org.jdrupes.vmoperator.common.K8sGenericStub.GenericSupplier; /** * A stub for secrets (v1). @@ -75,18 +74,7 @@ public class K8sV1ServiceStub extends K8sGenericStub { public static Collection list(K8sClient client, String namespace, ListOptions options) throws ApiException { return K8sGenericStub.list(V1Service.class, V1ServiceList.class, client, - CONTEXT, namespace, options, K8sV1ServiceStub::getGeneric); + CONTEXT, namespace, options, + (clnt, nscp, name) -> new K8sV1ServiceStub(clnt, nscp, name)); } - - /** - * Provide {@link GenericSupplier}. - */ - @SuppressWarnings({ "PMD.UnusedFormalParameter", - "PMD.UnusedPrivateMethod" }) - private static K8sV1ServiceStub getGeneric(Class objectClass, - Class objectListClass, K8sClient client, - APIResource context, String namespace, String name) { - return new K8sV1ServiceStub(client, namespace, name); - } - } \ No newline at end of file diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionModel.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionModel.java new file mode 100644 index 0000000..fa59c82 --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionModel.java @@ -0,0 +1,123 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.jdrupes.vmoperator.util.GsonPtr; + +/** + * Represents a VM definition. + */ +@SuppressWarnings("PMD.DataClass") +public class VmDefinitionModel extends K8sDynamicModel { + + /** + * Permissions for accessing and manipulating the VM. + */ + public enum Permission { + START("start"), STOP("stop"), ACCESS_CONSOLE("accessConsole"); + + @SuppressWarnings("PMD.UseConcurrentHashMap") + private static Map reprs = new HashMap<>(); + + static { + for (var value : EnumSet.allOf(Permission.class)) { + reprs.put(value.repr, value); + } + } + + private final String repr; + + Permission(String repr) { + this.repr = repr; + } + + /** + * Create permission from representation in CRD. + * + * @param value the value + * @return the permission + */ + @SuppressWarnings("PMD.AvoidLiteralsInIfCondition") + public static Set parse(String value) { + if ("*".equals(value)) { + return EnumSet.allOf(Permission.class); + } + return Set.of(reprs.get(value)); + } + + @Override + public String toString() { + return repr; + } + } + + /** + * Instantiates a new model from the JSON representation. + * + * @param delegate the gson instance to use for extracting structured data + * @param json the JSON + */ + public VmDefinitionModel(Gson delegate, JsonObject json) { + super(delegate, json); + } + + /** + * Collect all permissions for the given user with the given roles. + * + * @param user the user + * @param roles the roles + * @return the sets the + */ + public Set permissionsFor(String user, + Collection roles) { + return GsonPtr.to(data()) + .getAsListOf(JsonObject.class, "spec", "permissions") + .stream().filter(p -> GsonPtr.to(p).getAsString("user") + .map(u -> u.equals(user)).orElse(false) + || GsonPtr.to(p).getAsString("role").map(roles::contains) + .orElse(false)) + .map(p -> GsonPtr.to(p).getAsListOf(JsonPrimitive.class, "may") + .stream()) + .flatMap(Function.identity()).map(p -> p.getAsString()) + .map(Permission::parse).map(Set::stream) + .flatMap(Function.identity()).collect(Collectors.toSet()); + } + + /** + * Get the display password serial. + * + * @return the optional + */ + public Optional displayPasswordSerial() { + return GsonPtr.to(status()) + .get(JsonPrimitive.class, "displayPasswordSerial") + .map(JsonPrimitive::getAsLong); + } +} diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionModels.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionModels.java new file mode 100644 index 0000000..5ac412f --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionModels.java @@ -0,0 +1,39 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +/** + * Represents a list of {@link VmDefinitionModel}s. + */ +public class VmDefinitionModels + extends K8sDynamicModelsBase { + + /** + * Initialize the object list using the given JSON data. + * + * @param delegate the gson instance to use for extracting structured data + * @param data the data + */ + public VmDefinitionModels(Gson delegate, JsonObject data) { + super(VmDefinitionModel.class, delegate, data); + } +} diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionStub.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionStub.java new file mode 100644 index 0000000..49da3e0 --- /dev/null +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionStub.java @@ -0,0 +1,157 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.common; + +import io.kubernetes.client.Discovery.APIResource; +import io.kubernetes.client.apimachinery.GroupVersionKind; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.util.generic.options.ListOptions; +import java.io.Reader; +import java.util.Collection; + +/** + * A stub for namespaced custom objects. It uses a dynamic model + * (see {@link K8sDynamicModel}) for representing the object's + * state and can therefore be used for any kind of object, especially + * custom objects. + */ +@SuppressWarnings("PMD.DataflowAnomalyAnalysis") +public class VmDefinitionStub + extends K8sDynamicStubBase { + + private static DynamicTypeAdapterFactory taf = new VmDefintionModelTypeAdapterFactory(); + + /** + * Instantiates a new stub for VM defintions. + * + * @param client the client + * @param context the context + * @param namespace the namespace + * @param name the name + */ + public VmDefinitionStub(K8sClient client, APIResource context, + String namespace, String name) { + super(VmDefinitionModel.class, VmDefinitionModels.class, taf, client, + context, namespace, name); + } + + /** + * Get a dynamic object stub. If the version in parameter + * `gvk` is an empty string, the stub refers to the first object with + * matching group and kind. + * + * @param client the client + * @param gvk the group, version and kind + * @param namespace the namespace + * @param name the name + * @return the stub if the object exists + * @throws ApiException the api exception + */ + @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", + "PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" }) + public static VmDefinitionStub get(K8sClient client, + GroupVersionKind gvk, String namespace, String name) + throws ApiException { + return new VmDefinitionStub(client, apiResource(client, gvk), namespace, + name); + } + + /** + * Get a dynamic object stub. + * + * @param client the client + * @param context the context + * @param namespace the namespace + * @param name the name + * @return the stub if the object exists + * @throws ApiException the api exception + */ + @SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop", + "PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" }) + public static VmDefinitionStub get(K8sClient client, + APIResource context, String namespace, String name) { + return new VmDefinitionStub(client, context, namespace, name); + } + + /** + * Creates a stub from yaml. + * + * @param client the client + * @param context the context + * @param yaml the yaml + * @return the k 8 s dynamic stub + * @throws ApiException the api exception + */ + public static VmDefinitionStub createFromYaml(K8sClient client, + APIResource context, Reader yaml) throws ApiException { + var model = new VmDefinitionModel(client.getJSON().getGson(), + K8s.yamlToJson(client, yaml)); + return K8sGenericStub.create(VmDefinitionModel.class, + VmDefinitionModels.class, client, context, model, + (c, ns, n) -> new VmDefinitionStub(c, context, ns, n)); + } + + /** + * Get the stubs for the objects in the given namespace that match + * the criteria from the given options. + * + * @param client the client + * @param namespace the namespace + * @param options the options + * @return the collection + * @throws ApiException the api exception + */ + public static Collection list(K8sClient client, + APIResource context, String namespace, ListOptions options) + throws ApiException { + return K8sGenericStub.list(VmDefinitionModel.class, + VmDefinitionModels.class, client, context, namespace, options, + (c, ns, n) -> new VmDefinitionStub(c, context, ns, n)); + } + + /** + * Get the stubs for the objects in the given namespace. + * + * @param client the client + * @param namespace the namespace + * @return the collection + * @throws ApiException the api exception + */ + public static Collection list(K8sClient client, + APIResource context, String namespace) + throws ApiException { + return list(client, context, namespace, new ListOptions()); + } + + /** + * A factory for creating VmDefinitionModel(s) objects. + */ + public static class VmDefintionModelTypeAdapterFactory extends + DynamicTypeAdapterFactory { + + /** + * Instantiates a new dynamic model type adapter factory. + */ + public VmDefintionModelTypeAdapterFactory() { + super(VmDefinitionModel.class, VmDefinitionModels.class); + } + } + +} \ No newline at end of file diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/DisplayPasswordChanged.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/DisplayPasswordChanged.java deleted file mode 100644 index 9185bbc..0000000 --- a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/DisplayPasswordChanged.java +++ /dev/null @@ -1,76 +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 . - */ - -package org.jdrupes.vmoperator.manager.events; - -import io.kubernetes.client.openapi.models.V1Secret; -import org.jdrupes.vmoperator.common.K8sObserver.ResponseType; -import org.jgrapes.core.Channel; -import org.jgrapes.core.Components; -import org.jgrapes.core.Event; - -/** - * Indicates that a display secret has changed. - */ -@SuppressWarnings("PMD.DataClass") -public class DisplayPasswordChanged extends Event { - - private final ResponseType type; - private final V1Secret secret; - - /** - * Initializes a new display secret changed event. - * - * @param type the type - * @param secret the secret - */ - public DisplayPasswordChanged(ResponseType type, V1Secret secret) { - this.type = type; - this.secret = secret; - } - - /** - * Returns the type. - * - * @return the type - */ - public ResponseType type() { - return type; - } - - /** - * Gets the secret. - * - * @return the secret - */ - public V1Secret secret() { - return secret; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(Components.objectName(this)).append(" [") - .append(secret.getMetadata().getName()).append(' ').append(type); - if (channels() != null) { - builder.append(", channels=").append(Channel.toString(channels())); - } - builder.append(']'); - return builder.toString(); - } -} diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/GetDisplayPassword.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/GetDisplayPassword.java index 77dc298..37eddec 100644 --- a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/GetDisplayPassword.java +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/GetDisplayPassword.java @@ -19,40 +19,44 @@ package org.jdrupes.vmoperator.manager.events; import java.util.Optional; +import org.jdrupes.vmoperator.common.VmDefinitionModel; import org.jgrapes.core.Event; /** - * Gets the current display secret. + * Gets the current display secret and optionally updates it. */ @SuppressWarnings("PMD.DataClass") public class GetDisplayPassword extends Event { - private final String vmName; + private final VmDefinitionModel vmDef; /** * Instantiates a new returns the display secret. * - * @param vmName the vm name + * @param vmDef the vm name */ - public GetDisplayPassword(String vmName) { - this.vmName = vmName; + public GetDisplayPassword(VmDefinitionModel vmDef) { + this.vmDef = vmDef; } /** - * Gets the vm name. + * Gets the vm definition. * - * @return the vm name + * @return the vm definition */ - public String vmName() { - return vmName; + public VmDefinitionModel vmDefinition() { + return vmDef; } /** - * Return the password. Should only be called when the event is completed. + * Return the password. May only be called when the event is completed. * * @return the optional */ public Optional password() { + if (!isDone()) { + throw new IllegalStateException("Event is not done."); + } return currentResults().stream().findFirst(); } } diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmChannel.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmChannel.java index 972693a..46861ce 100644 --- a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmChannel.java +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmChannel.java @@ -19,7 +19,7 @@ package org.jdrupes.vmoperator.manager.events; import org.jdrupes.vmoperator.common.K8sClient; -import org.jdrupes.vmoperator.common.K8sDynamicModel; +import org.jdrupes.vmoperator.common.VmDefinitionModel; import org.jgrapes.core.Channel; import org.jgrapes.core.EventPipeline; import org.jgrapes.core.Subchannel.DefaultSubchannel; @@ -32,7 +32,7 @@ public class VmChannel extends DefaultSubchannel { private final EventPipeline pipeline; private final K8sClient client; - private K8sDynamicModel vmDefinition; + private VmDefinitionModel vmDefinition; private long generation = -1; /** @@ -56,7 +56,7 @@ public class VmChannel extends DefaultSubchannel { * @return the watch channel */ @SuppressWarnings("PMD.LinguisticNaming") - public VmChannel setVmDefinition(K8sDynamicModel definition) { + public VmChannel setVmDefinition(VmDefinitionModel definition) { this.vmDefinition = definition; return this; } @@ -66,7 +66,7 @@ public class VmChannel extends DefaultSubchannel { * * @return the json object */ - public K8sDynamicModel vmDefinition() { + public VmDefinitionModel vmDefinition() { return vmDefinition; } diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java index 5e93790..a2bafb7 100644 --- a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/VmDefChanged.java @@ -18,8 +18,8 @@ package org.jdrupes.vmoperator.manager.events; -import org.jdrupes.vmoperator.common.K8sDynamicModel; import org.jdrupes.vmoperator.common.K8sObserver; +import org.jdrupes.vmoperator.common.VmDefinitionModel; import org.jgrapes.core.Channel; import org.jgrapes.core.Components; import org.jgrapes.core.Event; @@ -36,7 +36,7 @@ public class VmDefChanged extends Event { private final K8sObserver.ResponseType type; private final boolean specChanged; - private final K8sDynamicModel vmDef; + private final VmDefinitionModel vmDef; /** * Instantiates a new VM changed event. @@ -46,7 +46,7 @@ public class VmDefChanged extends Event { * @param vmDefinition the VM definition */ public VmDefChanged(K8sObserver.ResponseType type, boolean specChanged, - K8sDynamicModel vmDefinition) { + VmDefinitionModel vmDefinition) { this.type = type; this.specChanged = specChanged; this.vmDef = vmDefinition; @@ -73,7 +73,7 @@ public class VmDefChanged extends Event { * * @return the object. */ - public K8sDynamicModel vmDefinition() { + public VmDefinitionModel vmDefinition() { return vmDef; } diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index 7dc07a2..a8b67a0 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -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)' diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview.md b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview.md new file mode 100644 index 0000000..50a3024 --- /dev/null +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview.md @@ -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. \ No newline at end of file diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview_de.md b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview_de.md new file mode 100644 index 0000000..e5e4d68 --- /dev/null +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview_de.md @@ -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. diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/l10n.properties b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/l10n.properties index ec22a06..6bcc3a2 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/l10n.properties +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/l10n.properties @@ -17,3 +17,4 @@ # consoleTitle = VM-Operator +introTitle = Usage \ No newline at end of file diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/l10n_de.properties b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/l10n_de.properties new file mode 100644 index 0000000..dcbba93 --- /dev/null +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/l10n_de.properties @@ -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 . +# + +introTitle = Benutzung diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/AvoidEmptyPolicy.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/AvoidEmptyPolicy.java index 000a21e..912b623 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/AvoidEmptyPolicy.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/AvoidEmptyPolicy.java @@ -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); } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java index 0c5f0cd..a882a79 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java @@ -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 diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java index a7b84a3..7de839b 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Constants.java @@ -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"; diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java index 89b5eac..66c11a7 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java @@ -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. diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplayPasswordMonitor.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplayPasswordMonitor.java deleted file mode 100644 index 9959aec..0000000 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplayPasswordMonitor.java +++ /dev/null @@ -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 . - */ - -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 { - - /** - * 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 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))); - } -} diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretMonitor.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretMonitor.java new file mode 100644 index 0000000..8bc1db0 --- /dev/null +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretMonitor.java @@ -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 . + */ + +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 { + + private int passwordValidity = 10; + private final List 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 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 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; + } + } +} diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretReconciler.java new file mode 100644 index 0000000..14b8890 --- /dev/null +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretReconciler.java @@ -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 . + */ + +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 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); + } + +} diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index 5ba9dc5..5bbfe38 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -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); } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java index 7027808..41f08ce 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java @@ -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 { +public class VmMonitor extends + AbstractMonitor { /** * 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 response) { + Watch.Response 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; diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index 06ed64c..bbcba5e 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -40,7 +40,8 @@ import static org.jdrupes.vmoperator.common.Constants.VM_OP_KIND_VM; import org.jdrupes.vmoperator.common.K8s; import org.jdrupes.vmoperator.common.K8sClient; import org.jdrupes.vmoperator.common.K8sDynamicModel; -import org.jdrupes.vmoperator.common.K8sDynamicStub; +import org.jdrupes.vmoperator.common.VmDefinitionModel; +import org.jdrupes.vmoperator.common.VmDefinitionStub; import org.jdrupes.vmoperator.runner.qemu.events.BalloonChangeEvent; import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; import org.jdrupes.vmoperator.runner.qemu.events.DisplayPasswordChanged; @@ -73,7 +74,7 @@ public class StatusUpdater extends Component { private long observedGeneration; private boolean guestShutdownStops; private boolean shutdownByGuest; - private K8sDynamicStub vmStub; + private VmDefinitionStub vmStub; /** * Instantiates a new status updater. @@ -158,7 +159,7 @@ public class StatusUpdater extends Component { return; } try { - vmStub = K8sDynamicStub.get(apiClient, + vmStub = VmDefinitionStub.get(apiClient, new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM), namespace, vmName); vmStub.model().ifPresent(model -> { @@ -226,7 +227,7 @@ public class StatusUpdater extends Component { "PMD.AvoidLiteralsInIfCondition" }) public void onRunnerStateChanged(RunnerStateChange event) throws ApiException { - K8sDynamicModel vmDef; + VmDefinitionModel vmDef; if (vmStub == null || (vmDef = vmStub.model().orElse(null)) == null) { return; } diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java index 1882173..a8bb1ae 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/VmConlet.java @@ -35,8 +35,8 @@ import java.util.Optional; import java.util.Set; import org.jdrupes.json.JsonBeanDecoder; import org.jdrupes.json.JsonDecodeException; -import org.jdrupes.vmoperator.common.K8sDynamicModel; import org.jdrupes.vmoperator.common.K8sObserver; +import org.jdrupes.vmoperator.common.VmDefinitionModel; import org.jdrupes.vmoperator.manager.events.ChannelCache; import org.jdrupes.vmoperator.manager.events.ModifyVm; import org.jdrupes.vmoperator.manager.events.VmChannel; @@ -69,7 +69,7 @@ public class VmConlet extends FreeMarkerConlet { private static final Set MODES = RenderMode.asSet( RenderMode.Preview, RenderMode.View); private final ChannelCache channelManager = new ChannelCache<>(); + VmDefinitionModel> channelManager = new ChannelCache<>(); private final TimeSeries summarySeries = new TimeSeries(Duration.ofDays(1)); private Summary cachedSummary; @@ -196,8 +196,8 @@ public class VmConlet extends FreeMarkerConlet { } } } else { - var vmDef = new K8sDynamicModel(channel.client().getJSON() - .getGson(), convertQuantities(event.vmDefinition().data())); + var vmDef = new VmDefinitionModel(channel.client().getJSON() + .getGson(), cleanup(event.vmDefinition().data())); channelManager.put(vmName, channel, vmDef); var def = JsonBeanDecoder.create(vmDef.data().toString()) .readObject(); @@ -220,7 +220,7 @@ public class VmConlet extends FreeMarkerConlet { } @SuppressWarnings("PMD.AvoidDuplicateLiterals") - private JsonObject convertQuantities(JsonObject vmDef) { + private JsonObject cleanup(JsonObject vmDef) { // Clone and remove managed fields var json = vmDef.deepCopy(); GsonPtr.to(json).to("metadata").get(JsonObject.class) diff --git a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss index 3649bff..a5658e9 100644 --- a/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss +++ b/org.jdrupes.vmoperator.vmconlet/src/org/jdrupes/vmoperator/vmconlet/browser/VmConlet-style.scss @@ -100,7 +100,7 @@ [role=button] { padding: 0.25rem; - &:not([aria-disabled]):hover { + &:not([aria-disabled]):hover, &[aria-disabled='false']:hover { box-shadow: var(--darkening); } } diff --git a/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n_de.properties b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n_de.properties index e81a0fe..f05dfff 100644 --- a/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n_de.properties +++ b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/l10n_de.properties @@ -3,5 +3,6 @@ conletName = VM-Konsole okayLabel = Anwenden und Schließen Select\ VM = VM auswählen -Start\ VM = VM Starten -Stop\ VM = VM Anhalten +Start\ VM = VM starten +Stop\ VM = VM anhalten +Open\ console = Konsole anzeigen diff --git a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java index b97ff6f..c06cc1a 100644 --- a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java +++ b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java @@ -1,6 +1,6 @@ /* * VM-Operator - * Copyright (C) 2023 Michael N. Lipp + * Copyright (C) 2023,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 @@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import freemarker.core.ParseException; import freemarker.template.MalformedTemplateNameException; @@ -33,18 +34,23 @@ import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; +import java.time.Duration; import java.util.Base64; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.ResourceBundle; import java.util.Set; import java.util.logging.Level; +import org.bouncycastle.util.Objects; import org.jdrupes.json.JsonBeanDecoder; import org.jdrupes.json.JsonDecodeException; import org.jdrupes.vmoperator.common.K8sDynamicModel; import org.jdrupes.vmoperator.common.K8sObserver; +import org.jdrupes.vmoperator.common.VmDefinitionModel; +import org.jdrupes.vmoperator.common.VmDefinitionModel.Permission; import org.jdrupes.vmoperator.manager.events.ChannelCache; import org.jdrupes.vmoperator.manager.events.GetDisplayPassword; import org.jdrupes.vmoperator.manager.events.ModifyVm; @@ -52,6 +58,7 @@ import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.util.GsonPtr; import org.jgrapes.core.Channel; +import org.jgrapes.core.Components; import org.jgrapes.core.Event; import org.jgrapes.core.Manager; import org.jgrapes.core.annotation.Handler; @@ -62,11 +69,15 @@ import org.jgrapes.util.events.KeyValueStoreUpdate; import org.jgrapes.webconsole.base.Conlet.RenderMode; import org.jgrapes.webconsole.base.ConletBaseModel; import org.jgrapes.webconsole.base.ConsoleConnection; +import org.jgrapes.webconsole.base.ConsoleRole; import org.jgrapes.webconsole.base.ConsoleUser; import org.jgrapes.webconsole.base.WebConsoleUtils; +import org.jgrapes.webconsole.base.events.AddConletRequest; import org.jgrapes.webconsole.base.events.AddConletType; import org.jgrapes.webconsole.base.events.AddPageResources.ScriptResource; import org.jgrapes.webconsole.base.events.ConletDeleted; +import org.jgrapes.webconsole.base.events.ConsoleConfigured; +import org.jgrapes.webconsole.base.events.ConsolePrepared; import org.jgrapes.webconsole.base.events.ConsoleReady; import org.jgrapes.webconsole.base.events.DeleteConlet; import org.jgrapes.webconsole.base.events.NotifyConletModel; @@ -75,22 +86,32 @@ import org.jgrapes.webconsole.base.events.OpenModalDialog; import org.jgrapes.webconsole.base.events.RenderConlet; import org.jgrapes.webconsole.base.events.RenderConletRequestBase; import org.jgrapes.webconsole.base.events.SetLocale; +import org.jgrapes.webconsole.base.events.UpdateConletType; import org.jgrapes.webconsole.base.freemarker.FreeMarkerConlet; /** * The Class VmConlet. */ @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports", - "PMD.CouplingBetweenObjects" }) + "PMD.CouplingBetweenObjects", "PMD.GodClass" }) public class VmViewer extends FreeMarkerConlet { + private static final String VM_NAME_PROPERTY = "vmName"; + private static final String RENDERED + = VmViewer.class.getName() + ".rendered"; + private static final String PENDING + = VmViewer.class.getName() + ".pending"; private static final Set MODES = RenderMode.asSet( RenderMode.Preview, RenderMode.Edit); + private static final Set MODES_FOR_GENERATED = RenderMode.asSet( + RenderMode.Preview, RenderMode.StickyPreview); private final ChannelCache channelManager = new ChannelCache<>(); + VmDefinitionModel> channelManager = new ChannelCache<>(); private static ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()); private Class preferredIpVersion = Inet4Address.class; + private final Set syncUsers = new HashSet<>(); + private final Set syncRoles = new HashSet<>(); /** * The periodically generated update event. @@ -114,24 +135,47 @@ public class VmViewer extends FreeMarkerConlet { * * @param event the event */ + @SuppressWarnings("unchecked") @Handler public void onConfigurationUpdate(ConfigurationUpdate event) { event.structured(componentPath()).ifPresent(c -> { - @SuppressWarnings("unchecked") - var dispRes = (Map) c - .getOrDefault("displayResource", Collections.emptyMap()); - switch ((String) dispRes.getOrDefault("preferredIpVersion", "")) { - case "ipv6": - preferredIpVersion = Inet6Address.class; - break; - case "ipv4": - default: - preferredIpVersion = Inet4Address.class; - break; + try { + var dispRes = (Map) c + .getOrDefault("displayResource", Collections.emptyMap()); + switch ((String) dispRes.getOrDefault("preferredIpVersion", + "")) { + case "ipv6": + preferredIpVersion = Inet6Address.class; + break; + case "ipv4": + default: + preferredIpVersion = Inet4Address.class; + break; + } + + // Sync + for (var entry : (List>) c.getOrDefault( + "syncPreviewsFor", Collections.emptyList())) { + if (entry.containsKey("user")) { + syncUsers.add(entry.get("user")); + } else if (entry.containsKey("role")) { + syncRoles.add(entry.get("role")); + } + } + } catch (ClassCastException e) { + logger.config("Malformed configuration: " + e.getMessage()); } }); } + private boolean syncPreviews(Session session) { + return WebConsoleUtils.userFromSession(session) + .filter(u -> syncUsers.contains(u.getName())).isPresent() + || WebConsoleUtils.rolesFromSession(session).stream() + .filter(cr -> syncRoles.contains(cr.getName())).findAny() + .isPresent(); + } + /** * On {@link ConsoleReady}, fire the {@link AddConletType}. * @@ -155,6 +199,61 @@ public class VmViewer extends FreeMarkerConlet { .addScript(new ScriptResource().setScriptType("module") .setScriptUri(event.renderSupport().conletResource( type(), "VmViewer-functions.js")))); + channel.session().put(RENDERED, new HashSet<>()); + } + + /** + * On console configured. + * + * @param event the event + * @param connection the console connection + * @throws InterruptedException the interrupted exception + */ + @Handler + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + public void onConsoleConfigured(ConsoleConfigured event, + ConsoleConnection connection) throws InterruptedException, + IOException { + @SuppressWarnings("unchecked") + final var rendered = (Set) connection.session().get(RENDERED); + connection.session().remove(RENDERED); + if (!syncPreviews(connection.session())) { + return; + } + + boolean foundMissing = false; + for (var vmName : accessibleVms(connection)) { + if (rendered.contains(vmName)) { + continue; + } + if (!foundMissing) { + // Suspending to allow rendering of conlets to be noticed + var failSafe = Components.schedule(t -> event.resumeHandling(), + Duration.ofSeconds(1)); + event.suspendHandling(failSafe::cancel); + connection.setAssociated(PENDING, event); + foundMissing = true; + } + fire(new AddConletRequest(event.event().event().renderSupport(), + VmViewer.class.getName(), + RenderMode.asSet(RenderMode.Preview)) + .addProperty(VM_NAME_PROPERTY, vmName), + connection); + } + } + + /** + * On console prepared. + * + * @param event the event + * @param connection the connection + */ + @Handler + public void onConsolePrepared(ConsolePrepared event, + ConsoleConnection connection) { + if (syncPreviews(connection.session())) { + connection.respond(new UpdateConletType(type())); + } } private String storagePath(Session session, String conletId) { @@ -163,6 +262,20 @@ public class VmViewer extends FreeMarkerConlet { + "/" + VmViewer.class.getName() + "/" + conletId; } + @Override + protected Optional createNewState(AddConletRequest event, + ConsoleConnection connection, String conletId) throws Exception { + var model = new ViewerModel(conletId); + model.vmName = (String) event.properties().get(VM_NAME_PROPERTY); + if (model.vmName != null) { + model.setGenerated(true); + } + String jsonState = objectMapper.writeValueAsString(model); + connection.respond(new KeyValueStoreUpdate().update( + storagePath(connection.session(), model.getConletId()), jsonState)); + return Optional.of(model); + } + @Override protected Optional createStateRepresentation(Event event, ConsoleConnection connection, String conletId) throws Exception { @@ -197,32 +310,57 @@ public class VmViewer extends FreeMarkerConlet { } @Override - @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + @SuppressWarnings({ "PMD.AvoidInstantiatingObjectsInLoops", "unchecked" }) protected Set doRenderConlet(RenderConletRequestBase event, - ConsoleConnection channel, String conletId, ViewerModel conletState) + ConsoleConnection channel, String conletId, ViewerModel model) throws Exception { ResourceBundle resourceBundle = resourceBundle(channel.locale()); Set renderedAs = new HashSet<>(); if (event.renderAs().contains(RenderMode.Preview)) { + channel.associated(PENDING, Event.class) + .ifPresent(e -> { + e.resumeHandling(); + channel.setAssociated(PENDING, null); + }); + + // Remove conlet if definition has been removed + if (model.vmName() != null + && !channelManager.associated(model.vmName()).isPresent()) { + channel.respond( + new DeleteConlet(conletId, Collections.emptySet())); + return Collections.emptySet(); + } + + // Don't render if user has not at least one permission + if (model.vmName() != null + && channelManager.associated(model.vmName()) + .map(d -> permissions(d, channel.session()).isEmpty()) + .orElse(true)) { + return Collections.emptySet(); + } + + // Render Template tpl = freemarkerConfig().getTemplate("VmViewer-preview.ftl.html"); channel.respond(new RenderConlet(type(), conletId, processTemplate(event, tpl, - fmModel(event, channel, conletId, conletState))) + fmModel(event, channel, conletId, model))) .setRenderAs( RenderMode.Preview.addModifiers(event.renderAs())) - .setSupportedModes(MODES)); + .setSupportedModes( + model.isGenerated() ? MODES_FOR_GENERATED : MODES)); renderedAs.add(RenderMode.Preview); - if (!Strings.isNullOrEmpty(conletState.vmName())) { - updateConfig(channel, conletState); + if (!Strings.isNullOrEmpty(model.vmName())) { + Optional.ofNullable(channel.session().get(RENDERED)) + .ifPresent(s -> ((Set) s).add(model.vmName())); + updateConfig(channel, model); } } if (event.renderAs().contains(RenderMode.Edit)) { Template tpl = freemarkerConfig() .getTemplate("VmViewer-edit.ftl.html"); - var fmModel = fmModel(event, channel, conletId, conletState); - fmModel.put("vmNames", - channelManager.keys().stream().sorted().toList()); + var fmModel = fmModel(event, channel, conletId, model); + fmModel.put("vmNames", accessibleVms(channel)); channel.respond(new OpenModalDialog(type(), conletId, processTemplate(event, tpl, fmModel)) .addOption("cancelable", true) @@ -232,6 +370,21 @@ public class VmViewer extends FreeMarkerConlet { return renderedAs; } + private List accessibleVms(ConsoleConnection channel) { + return channelManager.associated().stream() + .filter(d -> !permissions(d, channel.session()).isEmpty()) + .map(d -> d.getMetadata().getName()).sorted().toList(); + } + + private Set permissions(VmDefinitionModel vmDef, + Session session) { + var user = WebConsoleUtils.userFromSession(session) + .map(ConsoleUser::getName).orElse(null); + var roles = WebConsoleUtils.rolesFromSession(session) + .stream().map(ConsoleRole::getName).toList(); + return vmDef.permissionsFor(user, roles); + } + private void updateConfig(ConsoleConnection channel, ViewerModel model) { channel.respond(new NotifyConletView(type(), model.getConletId(), "updateConfig", model.vmName())); @@ -246,6 +399,9 @@ public class VmViewer extends FreeMarkerConlet { try { var def = JsonBeanDecoder.create(vmDef.data().toString()) .readObject(); + def.setField("userPermissions", + permissions(vmDef, channel.session()).stream() + .map(Permission::toString).toList()); channel.respond(new NotifyConletView(type(), model.getConletId(), "updateVmDefinition", def)); } catch (JsonDecodeException e) { @@ -279,8 +435,10 @@ public class VmViewer extends FreeMarkerConlet { "PMD.ConfusingArgumentToVarargsMethod" }) public void onVmDefChanged(VmDefChanged event, VmChannel channel) throws JsonDecodeException, IOException { - var vmDef = new K8sDynamicModel(channel.client().getJSON() + var vmDef = new VmDefinitionModel(channel.client().getJSON() .getGson(), event.vmDefinition().data()); + GsonPtr.to(vmDef.data()).to("metadata").get(JsonObject.class) + .remove("managedFields"); var vmName = vmDef.getMetadata().getName(); if (event.type() == K8sObserver.ResponseType.DELETED) { channelManager.remove(vmName); @@ -291,7 +449,8 @@ public class VmViewer extends FreeMarkerConlet { var connection = entry.getKey(); for (var conletId : entry.getValue()) { var model = stateFromSession(connection.session(), conletId); - if (model.isEmpty() || !model.get().vmName().equals(vmName)) { + if (model.isEmpty() + || !Objects.areEqual(model.get().vmName(), vmName)) { continue; } if (event.type() == K8sObserver.ResponseType.DELETED) { @@ -311,11 +470,15 @@ public class VmViewer extends FreeMarkerConlet { ConsoleConnection channel, ViewerModel model) throws Exception { event.stop(); - var vmName = event.params().asString(0); - var vmChannel = channelManager.channel(vmName).orElse(null); - if (vmChannel == null) { + var both = Optional.ofNullable(event.params().asString(0)) + .flatMap(vm -> channelManager.both(vm)); + if (both.isEmpty()) { return; } + var vmChannel = both.get().channel; + var vmDef = both.get().associated; + var vmName = vmDef.metadata().getName(); + var perms = permissions(vmDef, channel.session()); switch (event.method()) { case "selectedVm": model.setVmName(event.params().asString(0)); @@ -325,15 +488,22 @@ public class VmViewer extends FreeMarkerConlet { updateConfig(channel, model); break; case "start": - fire(new ModifyVm(vmName, "state", "Running", vmChannel)); + if (perms.contains(Permission.START)) { + fire(new ModifyVm(vmName, "state", "Running", vmChannel)); + } break; case "stop": - fire(new ModifyVm(vmName, "state", "Stopped", vmChannel)); + if (perms.contains(Permission.STOP)) { + fire(new ModifyVm(vmName, "state", "Stopped", vmChannel)); + } break; case "openConsole": - channelManager.channel(vmName).ifPresent( - vc -> fire(Event.onCompletion(new GetDisplayPassword(vmName), - ds -> openConsole(vmName, channel, model, ds)), vc)); + if (perms.contains(Permission.ACCESS_CONSOLE)) { + var pwQuery = Event.onCompletion(new GetDisplayPassword(vmDef), + e -> e.password().ifPresent( + pw -> openConsole(vmName, channel, model, pw))); + fire(pwQuery, vmChannel); + } break; default:// ignore break; @@ -341,7 +511,7 @@ public class VmViewer extends FreeMarkerConlet { } private void openConsole(String vmName, ConsoleConnection connection, - ViewerModel model, GetDisplayPassword pwQuery) { + ViewerModel model, String password) { var vmDef = channelManager.associated(vmName).orElse(null); if (vmDef == null) { return; @@ -362,10 +532,8 @@ public class VmViewer extends FreeMarkerConlet { StringBuffer data = new StringBuffer(100) .append("[virt-viewer]\ntype=spice\nhost=") .append(addr.get().getHostAddress()).append("\nport=") - .append(Integer.toString(port.get().getAsInt())).append('\n'); - pwQuery.password().ifPresent(p -> { - data.append("password=").append(p).append('\n'); - }); + .append(Integer.toString(port.get().getAsInt())) + .append("\npassword=").append(password).append('\n'); proxyUrl.map(JsonPrimitive::getAsString).ifPresent(u -> { if (!Strings.isNullOrEmpty(u)) { data.append("proxy=").append(u).append('\n'); @@ -418,9 +586,11 @@ public class VmViewer extends FreeMarkerConlet { /** * The Class VmsModel. */ + @SuppressWarnings("PMD.DataClass") public static class ViewerModel extends ConletBaseModel { private String vmName; + private boolean generated; /** * Instantiates a new vms model. @@ -450,5 +620,23 @@ public class VmViewer extends FreeMarkerConlet { this.vmName = vmName; } + /** + * Checks if is generated. + * + * @return the generated + */ + public boolean isGenerated() { + return generated; + } + + /** + * Sets the generated. + * + * @param generated the generated to set + */ + public void setGenerated(boolean generated) { + this.generated = generated; + } + } } diff --git a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-functions.ts b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-functions.ts index 2c66aeb..ca3b246 100644 --- a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-functions.ts +++ b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-functions.ts @@ -84,17 +84,22 @@ window.orgJDrupesVmOperatorVmViewer.initPreview = (previewDom: HTMLElement, + ? 'computer.svg' : 'computer-off.svg')" + :title="localize('Open console')"> - Date: Sun, 9 Jun 2024 22:54:42 +0200 Subject: [PATCH 141/462] Develop/v3 (#27) Prepare release. --- .github/workflows/release.yml | 3 +- README.md | 5 +- ....vmoperator.java-common-conventions.gradle | 8 +- ...s.vmoperator.versioning-conventions.gradle | 6 +- deploy/crds/vms-crd.yaml | 7 +- dev-example/Readme.md | 8 +- example/local-path/Readme.md | 8 +- example/rook-ceph/Readme.md | 4 +- misc/stylesheet.css | 62 +++++++------- .../vmoperator/common/VmDefinitionModel.java | 3 +- .../vmoperator/manager/events/ResetVm.java | 48 +++++++++++ org.jdrupes.vmoperator.manager/build.gradle | 4 +- .../manager/ManagerIntro-Preview.md | 4 +- .../manager/ManagerIntro-Preview_de.md | 2 +- .../vmoperator/manager/runnerConfig.ftl.yaml | 6 ++ .../manager/ConfigMapReconciler.java | 6 +- .../vmoperator/manager/Controller.java | 9 +-- .../manager/DisplaySecretMonitor.java | 4 +- .../manager/DisplaySecretReconciler.java | 2 +- .../vmoperator/manager/Reconciler.java | 25 +++++- .../jdrupes/vmoperator/manager/VmMonitor.java | 14 +++- .../vmoperator/manager/package-info.java | 25 +++++- .../config-sample.yaml | 6 ++ .../vmoperator/runner/qemu/Configuration.java | 3 + .../runner/qemu/DisplayController.java | 3 +- .../vmoperator/runner/qemu/Runner.java | 21 ++++- .../runner/qemu/commands/QmpReset.java | 43 ++++++++++ .../org/jdrupes/vmoperator/util/GsonPtr.java | 12 +++ .../vmconlet/VmConlet-view.ftl.html | 6 +- org.jdrupes.vmoperator.vmviewer/build.gradle | 2 +- .../vmviewer/VmViewer-confirmReset.ftl.html | 13 +++ .../vmviewer/VmViewer-edit.ftl.html | 9 ++- .../vmviewer/VmViewer-preview.ftl.html | 3 +- .../vmoperator/vmviewer/l10n.properties | 4 + .../vmoperator/vmviewer/l10n_de.properties | 5 ++ .../vmoperator/vmviewer/reset-icon.svg | 42 ++++++++++ .../jdrupes/vmoperator/vmviewer/VmViewer.java | 58 +++++++++++--- .../vmviewer/browser/VmViewer-functions.ts | 80 ++++++++++++------- .../vmviewer/browser/VmViewer-style.scss | 59 +++++++++++++- 39 files changed, 500 insertions(+), 132 deletions(-) create mode 100644 org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ResetVm.java create mode 100644 org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpReset.java create mode 100644 org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-confirmReset.ftl.html create mode 100644 org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/reset-icon.svg diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3e6b3c9..e99ee03 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,10 +18,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - ref: main - name: Install graphviz run: sudo apt-get install graphviz - name: Install podman diff --git a/README.md b/README.md index 1895bbb..176437a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Java CI with Gradle](https://github.com/mnlipp/VM-Operator/actions/workflows/gradle.yml/badge.svg)](https://github.com/mnlipp/VM-Operator/actions/workflows/gradle.yml) +[![Java CI with Gradle](https://github.com/mnlipp/VM-Operator/actions/workflows/gradle.yml/badge.svg)](https://github.com/mnlipp/VM-Operator/actions/workflows/gradle.yml) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/2277842dac894de4b663c6aa2779077e)](https://app.codacy.com/gh/mnlipp/VM-Operator/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) ![Latest Manager](https://img.shields.io/github/v/tag/mnlipp/vm-operator?filter=manager*&label=latest) ![Latest Runner](https://img.shields.io/github/v/tag/mnlipp/vm-operator?filter=runner-qemu*&label=latest) @@ -6,8 +6,7 @@ # Run Qemu in Kubernetes Pods The goal of this project is to provide the means for running Qemu -based VMs in Kubernetes pods. +based VMs in Kubernetes pods. See the [project's home page](https://mnlipp.github.io/VM-Operator/) for details. - diff --git a/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle index e09814c..5185041 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle @@ -5,6 +5,11 @@ */ plugins { + // Apply the common versioning conventions. + // Put this at the start, because accessing project.version before + // this is applied makes things fail. + id 'org.jdrupes.vmoperator.versioning-conventions' + // Apply the java Plugin to add support for Java. id 'java' @@ -13,9 +18,6 @@ plugins { // Access to git information id 'org.ajoberstar.grgit' - - // Apply the common versioning conventions. - id 'org.jdrupes.vmoperator.versioning-conventions' } repositories { diff --git a/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle index 114db51..40ced48 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle @@ -21,11 +21,13 @@ scmVersion { } var p = shortened.replace('.', '-') + "-" if (grgit.branch.current.name != "main" - && !grgit.branch.current.name.startsWith("release")) { + && grgit.branch.current.name != "HEAD" + && !grgit.branch.current.name.startsWith("release") + && !grgit.branch.current.name.startsWith("develop")) { p = p + grgit.branch.current.name.replace('/', '-') + "-" } prefix = p } } -version = scmVersion.version +project.version = scmVersion.version ext.isSnapshot = version.endsWith('-SNAPSHOT') diff --git a/deploy/crds/vms-crd.yaml b/deploy/crds/vms-crd.yaml index f441cbc..bfe3985 100644 --- a/deploy/crds/vms-crd.yaml +++ b/deploy/crds/vms-crd.yaml @@ -1012,7 +1012,12 @@ spec: type: array items: type: string - enum: ["start", "stop", "accessConsole", "*"] + enum: + - start + - stop + - reset + - accessConsole + - "*" default: [] vm: type: object diff --git a/dev-example/Readme.md b/dev-example/Readme.md index dfcd3e8..516fb7e 100644 --- a/dev-example/Readme.md +++ b/dev-example/Readme.md @@ -1,16 +1,16 @@ # Example setup for development -The CRD must be deployed independently. Apart from that, the +The CRD must be deployed independently. Apart from that, the `kustomize.yaml` * creates a small cdrom image repository and - + * deploys the operator in namespace `vmop-dev` with a replica of 0. - + This allows you to run the manager in your IDE. The `kustomize.yaml` also changes the container image repository for -the operator to a private repository for development. You have to +the operator to a private repository for development. You have to adapt this to your own repository if you also want to test your development version in a container. diff --git a/example/local-path/Readme.md b/example/local-path/Readme.md index 7afb948..bdba8cc 100644 --- a/example/local-path/Readme.md +++ b/example/local-path/Readme.md @@ -1,17 +1,17 @@ # Example setup -The CRD must be deployed independently. +The CRD must be deployed independently. ```sh kubectl apply -f https://github.com/mnlipp/VM-Operator/raw/main/deploy/crds/vms-crd.yaml ``` -Apart from that, the `kustomize.yaml` defines a namespace for the manager +Apart from that, the `kustomize.yaml` defines a namespace for the manager (and the VMs managed by it) and patches the repository PVC to create a small volume using local-path. -A second patch provides a new configuration file for the manager -that makes it use the local-path storage class when creating the +A second patch provides a new configuration file for the manager +that makes it use the local-path storage class when creating the small volume for a runner's data. The `kustomize.yaml` does not include the test VM. Before creating diff --git a/example/rook-ceph/Readme.md b/example/rook-ceph/Readme.md index 1d2cfc6..3756e93 100644 --- a/example/rook-ceph/Readme.md +++ b/example/rook-ceph/Readme.md @@ -1,12 +1,12 @@ # Example setup -The CRD must be deployed independently. +The CRD must be deployed independently. ```sh kubectl apply -f https://github.com/mnlipp/VM-Operator/raw/main/deploy/crds/vms-crd.yaml ``` -Apart from that, the `kustomize.yaml` defines a namespace for the manager +Apart from that, the `kustomize.yaml` defines a namespace for the manager (and the VMs managed by it) and applies patches to use `rook-cephfs` as storage class (instead of the default storage class). diff --git a/misc/stylesheet.css b/misc/stylesheet.css index e21b9b2..625b5ae 100644 --- a/misc/stylesheet.css +++ b/misc/stylesheet.css @@ -45,7 +45,7 @@ body { background-color:#ffffff; color:#353833; - font: normal 16px/1.5 "DejaVu Serif", serif; + font: normal 16px/1.5 "DejaVu Sans", Arial, Helvetica, sans-serif; margin:0; padding:0; height:100%; @@ -71,37 +71,33 @@ a[name] { color:#353833; } pre { - font-family: "DejaVu Sans Mono", monospace; + font-family:'DejaVu Sans Mono', monospace; } h1 { - font-family: "DejaVu Sans", sans; font-size:20px; } h2 { - font-family: "DejaVu Sans", sans; font-size:18px; } h3 { - font-family: "DejaVu Sans", sans; - font-size:16px; + font-size:17px; } h4 { - font-family: "DejaVu Sans", sans; - font-size:15px; + font-size:16px; + margin-top: 1rem; + margin-bottom: 1rem; } h5 { - font-family: "DejaVu Sans", sans; font-size:14px; } h6 { - font-family: "DejaVu Sans", sans; font-size:13px; } ul { list-style-type:disc; } code, tt { - font-family: "DejaVu Sans Mono", monospace; + font-family:'DejaVu Sans Mono', monospace; } :not(h1, h2, h3, h4, h5, h6) > code, :not(h1, h2, h3, h4, h5, h6) > tt { @@ -111,12 +107,12 @@ code, tt { line-height:1.4em; } dt code { - font-family: "DejaVu Sans Mono", monospace; + font-family:'DejaVu Sans Mono', monospace; font-size:14px; padding-top:4px; } .summary-table dt code { - font-family: "DejaVu Sans Mono", monospace; + font-family:'DejaVu Sans Mono', monospace; font-size:14px; vertical-align:top; padding-top:4px; @@ -124,7 +120,9 @@ dt code { sup { font-size:8px; } - +button { + font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif; +} /* * Styles for HTML generated by javadoc. * @@ -185,7 +183,6 @@ sup { min-height:2.8em; padding-top:10px; overflow:hidden; - font-family: "DejaVu Sans", sans; font-size:80%; } .sub-nav { @@ -193,7 +190,6 @@ sup { float:left; width:100%; overflow:hidden; - font-family: "DejaVu Sans", sans; font-size:80%; } .sub-nav div { @@ -311,13 +307,16 @@ main { position:relative; } dl.notes > dt { - font-family: "DejaVu Sans", sans; + font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif; + /* font-size:12px; */ font-weight:bold; margin:10px 0 0 0; color:#4E4E4E; } dl.notes > dd { - margin:5px 10px 10px 0; + margin:5px 10px 0 0; + /* font-size:14px; */ + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; } dl.name-value > dt { margin-left:1px; @@ -389,6 +388,11 @@ ul.see-list-long li:not(:last-child):after { border-bottom:1px solid #EEE; padding:0; } + +.summary-table .col-first { + font-family: "DejaVu Sans Mono", monospace; +} + .caption { position:relative; text-align:left; @@ -402,7 +406,6 @@ ul.see-list-long li:not(:last-child):after { padding-left:1px; margin:0; white-space:pre; - font-family: 'DejaVu Sans'; } .caption a:link, .caption a:visited { color:#1f389c; @@ -450,9 +453,6 @@ div.table-tabs > button.table-tab { display: grid; grid-template-columns: minmax(10%, max-content) minmax(15%, max-content) minmax(15%, auto); } -#method-summary-table .three-column-summary { - grid-template-columns: minmax(10%, 20%) minmax(15%, max-content) minmax(15%, auto); -} .four-column-summary { display: grid; grid-template-columns: minmax(10%, max-content) minmax(10%, max-content) minmax(10%, max-content) minmax(10%, auto); @@ -490,7 +490,6 @@ div.table-tabs > button.table-tab { } .table-header { background:#dee3e9; - font-family: 'DejaVu Sans'; font-weight: bold; } /* @@ -508,7 +507,6 @@ div.table-tabs > button.table-tab { .col-last { white-space:normal; } -/* .col-first a:link, .col-first a:visited, .col-second a:link, .col-second a:visited, .col-first a:link, .col-first a:visited, @@ -520,7 +518,6 @@ div.table-tabs > button.table-tab { .all-packages-container a:link, .all-packages-container a:visited { font-weight:bold; } -*/ .table-sub-heading-color { background-color:#EEEEFF; } @@ -537,12 +534,9 @@ div.table-tabs > button.table-tab { margin:0; padding:10px 0; } -/* div.block { - font-size:14px; font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; } -*/ .col-last div { padding-top:0; } @@ -553,8 +547,7 @@ div.block { .package-signature, .type-signature, .member-signature { - font-family: "DejaVu Sans Mono", monospace; - /* font-size:14px; */ + font-family:'DejaVu Sans Mono', monospace; margin:14px 0; white-space: pre-wrap; } @@ -593,13 +586,8 @@ h1.hidden { .deprecated-label, .descfrm-type-label, .implementation-label, .member-name-label, .member-name-link, .module-label-in-package, .module-label-in-type, .override-specify-label, .package-label-in-type, .package-hierarchy-label, .type-name-label, .type-name-link, .search-tag-link, .preview-label { - font-family: "DejaVu Sans", sans; font-weight:bold; } -.sub-title, .inheritance, .all-packages-table-tab1.col-first, - .summary-table .col-first { - font-family: "DejaVu Sans", sans; -} .deprecation-comment, .help-footnote, .preview-comment { font-style:italic; } @@ -658,6 +646,7 @@ main, nav, header, footer, section { ul.ui-autocomplete { position:fixed; z-index:999999; + background-color: #FFFFFF; } ul.ui-autocomplete li { float:left; @@ -667,6 +656,9 @@ ul.ui-autocomplete li { .result-highlight { font-weight:bold; } +.ui-autocomplete .result-item { + font-size: inherit; +} #search-input { background-image:url('resources/glass.png'); background-size:13px; diff --git a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionModel.java b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionModel.java index fa59c82..5e1ebb0 100644 --- a/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionModel.java +++ b/org.jdrupes.vmoperator.common/src/org/jdrupes/vmoperator/common/VmDefinitionModel.java @@ -41,7 +41,8 @@ public class VmDefinitionModel extends K8sDynamicModel { * Permissions for accessing and manipulating the VM. */ public enum Permission { - START("start"), STOP("stop"), ACCESS_CONSOLE("accessConsole"); + START("start"), STOP("stop"), RESET("reset"), + ACCESS_CONSOLE("accessConsole"); @SuppressWarnings("PMD.UseConcurrentHashMap") private static Map reprs = new HashMap<>(); diff --git a/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ResetVm.java b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ResetVm.java new file mode 100644 index 0000000..f3320c8 --- /dev/null +++ b/org.jdrupes.vmoperator.manager.events/src/org/jdrupes/vmoperator/manager/events/ResetVm.java @@ -0,0 +1,48 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.manager.events; + +import org.jgrapes.core.Event; + +/** + * Triggers a reset of the VM. + */ +@SuppressWarnings("PMD.DataClass") +public class ResetVm extends Event { + + private final String vmName; + + /** + * Instantiates a new event. + * + * @param vmName the vm name + */ + public ResetVm(String vmName) { + this.vmName = vmName; + } + + /** + * Gets the vm name. + * + * @return the vm name + */ + public String vmName() { + return vmName; + } +} diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index a8b67a0..1887108 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -18,10 +18,10 @@ dependencies { implementation 'org.jgrapes:org.jgrapes.http:[3.1.0,4)' implementation 'org.jgrapes:org.jgrapes.util:[1.34.0,2)' - implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.5.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.7.0,2)' 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.oidclogin:[1.4.0,2)' implementation 'org.jgrapes:org.jgrapes.webconlet.markdowndisplay:[1.2.0,2)' runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.sysinfo:[1.4.0,2)' diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview.md b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview.md index 50a3024..b6b9efa 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview.md +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview.md @@ -1,5 +1,5 @@ -You can use the "puzzle piece" icon on the top right corner of the +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. \ No newline at end of file +conlet (if available) to get a detailed view. diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview_de.md b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview_de.md index e5e4d68..bec5f3e 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview_de.md +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/ManagerIntro-Preview_de.md @@ -1,4 +1,4 @@ -Verwenden Sie das "Puzzle"-Icon auf der rechten oberen Ecke +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 diff --git a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml index 7679a68..253f9b7 100644 --- a/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml +++ b/org.jdrupes.vmoperator.manager/resources/org/jdrupes/vmoperator/manager/runnerConfig.ftl.yaml @@ -48,6 +48,12 @@ data: # Whether a shutdown initiated by the guest stops the pod deployment guestShutdownStops: ${ cr.spec.guestShutdownStops!false?c } + # When incremented, the VM is reset. The value has no default value, + # i.e. if you start the VM without a value for this property, and + # decide to trigger a reset later, you have to first set the value + # and then inrement it. + resetCounter: ${ cr.resetCount } + # Forward the cloud-init data if provided <#if cr.spec.cloudInit??> cloudInit: diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java index a882a79..4219e53 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/ConfigMapReconciler.java @@ -36,7 +36,6 @@ import org.jdrupes.vmoperator.common.K8s; import static org.jdrupes.vmoperator.manager.Constants.APP_NAME; import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME; import org.jdrupes.vmoperator.manager.events.VmChannel; -import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; @@ -62,7 +61,6 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; /** * Reconcile. * - * @param event the event * @param model the model * @param channel the channel * @return the dynamic kubernetes object @@ -70,8 +68,8 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; * @throws TemplateException the template exception * @throws ApiException the api exception */ - public DynamicKubernetesObject reconcile(VmDefChanged event, - Map model, VmChannel channel) + public DynamicKubernetesObject reconcile(Map model, + VmChannel channel) throws IOException, TemplateException, ApiException { // Get API DynamicKubernetesApi cmApi = new DynamicKubernetesApi("", "v1", diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java index 66c11a7..86e3751 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Controller.java @@ -181,13 +181,12 @@ public class Controller extends Component { @Handler public void onModifyVm(ModifyVm event, VmChannel channel) throws ApiException, IOException { - patchVmSpec(channel.client(), event.name(), event.path(), + patchVmDef(channel.client(), event.name(), "spec/vm/" + event.path(), event.value()); } - private void patchVmSpec(K8sClient client, String name, String path, - Object value) - throws ApiException, IOException { + private void patchVmDef(K8sClient client, String name, String path, + Object value) throws ApiException, IOException { var vmStub = K8sDynamicStub.get(client, new GroupVersionKind(VM_OP_GROUP, "", VM_OP_KIND_VM), namespace, name); @@ -197,7 +196,7 @@ public class Controller extends Component { ? "\"" + value + "\"" : value.toString(); var res = vmStub.patch(V1Patch.PATCH_FORMAT_JSON_PATCH, - new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/vm/" + new V1Patch("[{\"op\": \"replace\", \"path\": \"/" + path + "\", \"value\": " + valueAsText + "}]"), client.defaultPatchOptions()); if (!res.isPresent()) { diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretMonitor.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretMonitor.java index 8bc1db0..1ea766c 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretMonitor.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretMonitor.java @@ -33,6 +33,7 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Scanner; import java.util.logging.Level; import static org.jdrupes.vmoperator.common.Constants.APP_NAME; @@ -180,7 +181,8 @@ public class DisplaySecretMonitor // Check validity var model = stub.model().get(); @SuppressWarnings("PMD.StringInstantiation") - var expiry = new String(model.getData().get(DATA_PASSWORD_EXPIRY)); + var expiry = Optional.ofNullable(model.getData() + .get(DATA_PASSWORD_EXPIRY)).map(b -> new String(b)).orElse(null); if (model.getData().get(DATA_DISPLAY_PASSWORD) != null && stillValid(expiry)) { event.setResult( diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretReconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretReconciler.java index 14b8890..17456aa 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretReconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/DisplaySecretReconciler.java @@ -64,7 +64,7 @@ import org.jose4j.base64url.Base64; var display = GsonPtr.to(event.vmDefinition().data()).to("spec", "vm", "display"); if (!display.get(JsonPrimitive.class, "spice", "generateSecret") - .map(JsonPrimitive::getAsBoolean).orElse(false)) { + .map(JsonPrimitive::getAsBoolean).orElse(true)) { return; } diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index 5bbfe38..437790b 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -51,6 +51,7 @@ import org.jdrupes.vmoperator.common.K8sDynamicModel; import org.jdrupes.vmoperator.common.K8sObserver; import org.jdrupes.vmoperator.common.K8sV1SecretStub; import static org.jdrupes.vmoperator.manager.Constants.COMP_DISPLAY_SECRET; +import org.jdrupes.vmoperator.manager.events.ResetVm; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.util.ExtendedObjectWrapper; @@ -209,13 +210,35 @@ public class Reconciler extends Component { // Reconcile, use "augmented" vm definition for model Map model = prepareModel(channel.client(), patchCr(event.vmDefinition())); - var configMap = cmReconciler.reconcile(event, model, channel); + var configMap = cmReconciler.reconcile(model, channel); model.put("cm", configMap.getRaw()); dsReconciler.reconcile(event, model, channel); stsReconciler.reconcile(event, model, channel); lbReconciler.reconcile(event, model, channel); } + /** + * Reset the VM by incrementing the reset count and doing a + * partial reconcile (configmap only). + * + * @param event the event + * @param channel the channel + * @throws IOException + * @throws ApiException + * @throws TemplateException + */ + @Handler + public void onResetVm(ResetVm event, VmChannel channel) + throws ApiException, IOException, TemplateException { + var defRoot + = GsonPtr.to(channel.vmDefinition().data()).get(JsonObject.class); + defRoot.addProperty("resetCount", + defRoot.get("resetCount").getAsLong() + 1); + Map model + = prepareModel(channel.client(), patchCr(channel.vmDefinition())); + cmReconciler.reconcile(model, channel); + } + private DynamicKubernetesObject patchCr(K8sDynamicModel vmDef) { var json = vmDef.data().deepCopy(); // Adjust cdromImage path diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java index 41f08ce..e049b17 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/VmMonitor.java @@ -25,13 +25,13 @@ import io.kubernetes.client.openapi.models.V1ObjectMeta; import io.kubernetes.client.util.Watch; import io.kubernetes.client.util.generic.options.ListOptions; import java.io.IOException; +import java.util.Optional; import java.util.Set; import java.util.logging.Level; import java.util.stream.Collectors; 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.K8sDynamicStub; import org.jdrupes.vmoperator.common.K8sObserver.ResponseType; import org.jdrupes.vmoperator.common.K8sV1ConfigMapStub; @@ -121,7 +121,7 @@ public class VmMonitor extends } if (vmDef.data() != null) { // New data, augment and save - addDynamicData(channel.client(), vmDef); + addDynamicData(channel.client(), vmDef, channel.vmDefinition()); channel.setVmDefinition(vmDef); } else { // Reuse cached @@ -151,8 +151,16 @@ public class VmMonitor extends } } - private void addDynamicData(K8sClient client, K8sDynamicModel vmState) { + private void addDynamicData(K8sClient client, VmDefinitionModel vmState, + VmDefinitionModel prevState) { var rootNode = GsonPtr.to(vmState.data()).get(JsonObject.class); + + // Maintain (or initialize) the resetCount + rootNode.addProperty("resetCount", Optional.ofNullable(prevState) + .map(ps -> GsonPtr.to(ps.data())) + .flatMap(d -> d.getAsLong("resetCount")).orElse(0L)); + + // Add defaults in case the VM is not running rootNode.addProperty("nodeName", ""); rootNode.addProperty("nodeAddress", ""); diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java index 54d4efe..337b5e3 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/package-info.java @@ -57,7 +57,7 @@ * ``` * * Developers may also be interested in the usage of channels - * by the application's component: + * by the application's components: * * ![Main channels](app-channels.svg) * @@ -74,6 +74,8 @@ * * Component NioDispatcher as NioDispatcher <> * [Manager] *-up- [NioDispatcher] + * Component HttpConnector as HttpConnector <> + * [Manager] *-up- [HttpConnector] * Component FileSystemWatcher as FileSystemWatcher <> * [Manager] *-up- [FileSystemWatcher] * Component YamlConfigurationStore as YamlConfigurationStore <> @@ -119,6 +121,7 @@ * [WebConsole] *-- [RoleConfigurator] * [WebConsole] *-- [RoleConletFilter] * [WebConsole] *-left- [LoginConlet] + * [WebConsole] *-right- [OidcClient] * * Component "ComponentCollector\nfor page resources" as cpr <> * [WebConsole] *-- [cpr] @@ -147,21 +150,35 @@ * () "guiTransport" as hT * hT .up. [GuiSocketServer:8080] * hT .down. [GuiHttpServer] + * hT .right[hidden]. [HttpConnector] * * [YamlConfigurationStore] -right[hidden]- hT * * () "guiHttp" as http * http .up. [GuiHttpServer] + * http .up. [HttpConnector] + * note top of [HttpConnector]: transport layer com-\nponents omitted * - * [PreferencesStore] .right. http + * [PreferencesStore] .. http + * [OidcClient] .up. http + * [LanguageSelector] .left. http * [InMemorySessionManager] .up. http - * [LanguageSelector] .up. http * * package "Conceptual WebConsole" { - * [ConsoleWeblet] .left. http + * [ConsoleWeblet] .right. http * [ConsoleWeblet] *-down- [WebConsole] * } * + * [Controller] .down[hidden]. [ConsoleWeblet] + * + * () "console" as console + * console .. WebConsole + * + * [OidcClient] .. console + * [LoginConlet] .right. console + * + * note right of console: More conlets\nconnect here + * * @enduml */ package org.jdrupes.vmoperator.manager; diff --git a/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml b/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml index c365a12..e23a2ec 100644 --- a/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml +++ b/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml @@ -45,6 +45,12 @@ # property in the CRD. # "guestShutdownStops": # false + + # When incremented, the VM is reset. The value has no default value, + # i.e. if you start the VM without a value for this property, and + # decide to trigger a reset later, you have to first set the value + # and then inrement it. + # "resetCounter": 1 # Define the VM (required) "vm": diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java index d6d5219..4e89944 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Configuration.java @@ -82,6 +82,9 @@ public class Configuration implements Dto { /** If guest shutdown changes CRD .vm.state to "Stopped". */ public boolean guestShutdownStops; + /** Increments of the reset counter trigger a reset of the VM. */ + public Integer resetCounter; + /** The vm. */ @SuppressWarnings("PMD.ShortVariable") public Vm vm; diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java index 304ea04..dc73cb2 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java @@ -116,8 +116,9 @@ public class DisplayController extends Component { } if (Objects.equals(this.currentPassword, password)) { - return false; + return true; } + this.currentPassword = password; logger.fine(() -> "Updating display password"); fire(new MonitorCommand(new QmpSetDisplayPassword(protocol, password))); return true; diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index e0baa4f..4d9f479 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -55,6 +55,7 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import static org.jdrupes.vmoperator.common.Constants.APP_NAME; import org.jdrupes.vmoperator.runner.qemu.commands.QmpCont; +import org.jdrupes.vmoperator.runner.qemu.commands.QmpReset; import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; import org.jdrupes.vmoperator.runner.qemu.events.Exit; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; @@ -215,6 +216,7 @@ public class Runner extends Component { private CommandDefinition cloudInitImgDefinition; private CommandDefinition qemuDefinition; private final QemuMonitor qemuMonitor; + private Integer resetCounter; private State state = State.INITIALIZING; /** Preparatory actions for QEMU start */ @@ -615,7 +617,7 @@ public class Runner extends Component { * @param event the event */ @Handler(priority = -1000) - public void onConfigureQemu(ConfigureQemu event) { + public void onConfigureQemuFinal(ConfigureQemu event) { if (state == State.STARTING) { fire(new MonitorCommand(new QmpCont())); state = State.RUNNING; @@ -624,6 +626,23 @@ public class Runner extends Component { } } + /** + * On configure qemu. + * + * @param event the event + */ + @Handler + public void onConfigureQemu(ConfigureQemu event) { + if (state == State.RUNNING) { + if (resetCounter != null + && event.configuration().resetCounter != null + && event.configuration().resetCounter > resetCounter) { + fire(new MonitorCommand(new QmpReset())); + } + resetCounter = event.configuration().resetCounter; + } + } + /** * On process exited. * diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpReset.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpReset.java new file mode 100644 index 0000000..0bcffc4 --- /dev/null +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/commands/QmpReset.java @@ -0,0 +1,43 @@ +/* + * 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 . + */ + +package org.jdrupes.vmoperator.runner.qemu.commands; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * A {@link QmpCommand} that send a system_reset to the VM. + */ +public class QmpReset extends QmpCommand { + + @SuppressWarnings({ "PMD.FieldNamingConventions", + "PMD.VariableNamingConventions" }) + private static final JsonNode jsonTemplate + = parseJson("{ \"execute\": \"system_reset\" }"); + + @Override + public JsonNode toJson() { + return jsonTemplate.deepCopy(); + } + + @Override + public String toString() { + return "QmpReset()"; + } + +} diff --git a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java index e3d9fcd..8b84ed3 100644 --- a/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java +++ b/org.jdrupes.vmoperator.util/src/org/jdrupes/vmoperator/util/GsonPtr.java @@ -265,6 +265,18 @@ public class GsonPtr { return set(selector, new JsonPrimitive(value)); } + /** + * Short for `set(selector, new JsonPrimitive(value))`. + * + * @param selector the selector + * @param value the value + * @return the gson ptr + * @see #set(Object, JsonElement) + */ + public GsonPtr set(Object selector, Long value) { + return set(selector, new JsonPrimitive(value)); + } + /** * Short for `set(selector, new JsonPrimitive(value))`. * diff --git a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html index 913f45d..708a1a3 100644 --- a/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html +++ b/org.jdrupes.vmoperator.vmconlet/resources/org/jdrupes/vmoperator/vmconlet/VmConlet-view.ftl.html @@ -52,12 +52,14 @@ v-html="controller.breakBeforeDots(entry[key])"> - - +

${_("confirmResetMsg")}

+

+ + + + + + +

+ \ No newline at end of file diff --git a/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-edit.ftl.html b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-edit.ftl.html index d4e86ca..e86d9db 100644 --- a/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-edit.ftl.html +++ b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-edit.ftl.html @@ -1,7 +1,8 @@ -
+
{{ localize("Select VM") }} diff --git a/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-preview.ftl.html b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-preview.ftl.html index 1cd0392..c034504 100644 --- a/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-preview.ftl.html +++ b/org.jdrupes.vmoperator.vmviewer/resources/org/jdrupes/vmoperator/vmviewer/VmViewer-preview.ftl.html @@ -1,4 +1,5 @@ -
+ + + + + + diff --git a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java index c06cc1a..fe48d3b 100644 --- a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java +++ b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java @@ -20,6 +20,7 @@ package org.jdrupes.vmoperator.vmviewer; import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.google.gson.JsonObject; @@ -54,6 +55,7 @@ import org.jdrupes.vmoperator.common.VmDefinitionModel.Permission; import org.jdrupes.vmoperator.manager.events.ChannelCache; import org.jdrupes.vmoperator.manager.events.GetDisplayPassword; import org.jdrupes.vmoperator.manager.events.ModifyVm; +import org.jdrupes.vmoperator.manager.events.ResetVm; import org.jdrupes.vmoperator.manager.events.VmChannel; import org.jdrupes.vmoperator.manager.events.VmDefChanged; import org.jdrupes.vmoperator.util.GsonPtr; @@ -90,10 +92,10 @@ import org.jgrapes.webconsole.base.events.UpdateConletType; import org.jgrapes.webconsole.base.freemarker.FreeMarkerConlet; /** - * The Class VmConlet. + * The Class VmViewer. */ @SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.ExcessiveImports", - "PMD.CouplingBetweenObjects", "PMD.GodClass" }) + "PMD.CouplingBetweenObjects", "PMD.GodClass", "PMD.TooManyMethods" }) public class VmViewer extends FreeMarkerConlet { private static final String VM_NAME_PROPERTY = "vmName"; @@ -465,12 +467,19 @@ public class VmViewer extends FreeMarkerConlet { @Override @SuppressWarnings({ "PMD.AvoidDecimalLiteralsInBigDecimalConstructor", - "PMD.ConfusingArgumentToVarargsMethod" }) + "PMD.ConfusingArgumentToVarargsMethod", "PMD.NcssCount", + "PMD.AvoidLiteralsInIfCondition" }) protected void doUpdateConletState(NotifyConletModel event, ConsoleConnection channel, ViewerModel model) throws Exception { event.stop(); - var both = Optional.ofNullable(event.params().asString(0)) + if ("selectedVm".equals(event.method())) { + selectVm(event, channel, model); + return; + } + + // Handle command for selected VM + var both = Optional.ofNullable(model.vmName()) .flatMap(vm -> channelManager.both(vm)); if (both.isEmpty()) { return; @@ -479,14 +488,8 @@ public class VmViewer extends FreeMarkerConlet { var vmDef = both.get().associated; var vmName = vmDef.metadata().getName(); var perms = permissions(vmDef, channel.session()); + var resourceBundle = resourceBundle(channel.locale()); switch (event.method()) { - case "selectedVm": - model.setVmName(event.params().asString(0)); - String jsonState = objectMapper.writeValueAsString(model); - channel.respond(new KeyValueStoreUpdate().update(storagePath( - channel.session(), model.getConletId()), jsonState)); - updateConfig(channel, model); - break; case "start": if (perms.contains(Permission.START)) { fire(new ModifyVm(vmName, "state", "Running", vmChannel)); @@ -497,6 +500,16 @@ public class VmViewer extends FreeMarkerConlet { fire(new ModifyVm(vmName, "state", "Stopped", vmChannel)); } break; + case "reset": + if (perms.contains(Permission.RESET)) { + confirmReset(event, channel, model, resourceBundle); + } + break; + case "resetConfirmed": + if (perms.contains(Permission.RESET)) { + fire(new ResetVm(vmName), vmChannel); + } + break; case "openConsole": if (perms.contains(Permission.ACCESS_CONSOLE)) { var pwQuery = Event.onCompletion(new GetDisplayPassword(vmDef), @@ -510,6 +523,15 @@ public class VmViewer extends FreeMarkerConlet { } } + private void selectVm(NotifyConletModel event, ConsoleConnection channel, + ViewerModel model) throws JsonProcessingException { + model.setVmName(event.params().asString(0)); + String jsonState = objectMapper.writeValueAsString(model); + channel.respond(new KeyValueStoreUpdate().update(storagePath( + channel.session(), model.getConletId()), jsonState)); + updateConfig(channel, model); + } + private void openConsole(String vmName, ConsoleConnection connection, ViewerModel model, String password) { var vmDef = channelManager.associated(vmName).orElse(null); @@ -577,6 +599,20 @@ public class VmViewer extends FreeMarkerConlet { .findFirst().or(() -> addrs.stream().findFirst()); } + private void confirmReset(NotifyConletModel event, + ConsoleConnection channel, ViewerModel model, + ResourceBundle resourceBundle) throws TemplateNotFoundException, + MalformedTemplateNameException, ParseException, IOException { + Template tpl = freemarkerConfig() + .getTemplate("VmViewer-confirmReset.ftl.html"); + channel.respond(new OpenModalDialog(type(), model.getConletId(), + processTemplate(event, tpl, + fmModel(event, channel, model.getConletId(), model))) + .addOption("cancelable", true).addOption("closeLabel", "") + .addOption("title", + resourceBundle.getString("confirmResetTitle"))); + } + @Override protected boolean doSetLocale(SetLocale event, ConsoleConnection channel, String conletId) throws Exception { diff --git a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-functions.ts b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-functions.ts index ca3b246..a14e83c 100644 --- a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-functions.ts +++ b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-functions.ts @@ -31,8 +31,9 @@ declare global { interface Window { orgJDrupesVmOperatorVmViewer: { initPreview?: (previewDom: HTMLElement, isUpdate: boolean) => void, - initEdit?: (viewDom: HTMLElement, isUpdate: boolean) => void - applyEdit?: (viewDom: HTMLElement, apply: boolean) => void + initEdit?: (viewDom: HTMLElement, isUpdate: boolean) => void, + applyEdit?: (viewDom: HTMLElement, apply: boolean) => void, + confirmReset?: (conletType: string, conletId: string) => void } } } @@ -63,7 +64,16 @@ window.orgJDrupesVmOperatorVmViewer.initPreview = (previewDom: HTMLElement, vmName: "", vmDefinition: {} }); - const vmDef = computed(() => previewApi.vmDefinition); + const configured = computed(() => previewApi.vmDefinition.spec); + const startable = computed(() => previewApi.vmDefinition.spec && + previewApi.vmDefinition.spec.vm.state !== 'Running' + && !previewApi.vmDefinition.running); + const stoppable = computed(() => previewApi.vmDefinition.spec && + previewApi.vmDefinition.spec.vm.state !== 'Stopped' + && previewApi.vmDefinition.running); + const running = computed(() => previewApi.vmDefinition.running); + const permissions = computed(() => previewApi.vmDefinition.spec + ? previewApi.vmDefinition.userPermissions : []); watch(() => previewApi.vmName, (name: string) => { if (name !== "") { @@ -73,41 +83,51 @@ window.orgJDrupesVmOperatorVmViewer.initPreview = (previewDom: HTMLElement, provideApi(previewDom, previewApi); - const vmAction = (vmName: string, action: string) => { - JGConsole.notifyConletModel(conletId, action, vmName); + const vmAction = (action: string) => { + JGConsole.notifyConletModel(conletId, action); }; - return { localize, resourceBase, vmDef, vmAction }; + return { localize, resourceBase, vmAction, configured, + startable, stoppable, running, permissions }; }, template: ` - - + - + + +
- + - - + - - + v-on:click="vmAction('stop')"> + + + + + +
` }); @@ -209,3 +229,9 @@ window.orgJDrupesVmOperatorVmViewer.applyEdit = const vmName = getApi>(dialogDom!)!.value; JGConsole.notifyConletModel(conletId, "selectedVm", vmName); } + +window.orgJDrupesVmOperatorVmViewer.confirmReset = + (conletType: string, conletId: string) => { + JGConsole.instance.closeModalDialog(conletType, conletId); + JGConsole.notifyConletModel(conletId, "resetConfirmed"); +} \ No newline at end of file diff --git a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-style.scss b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-style.scss index 83e92a5..6d0654f 100644 --- a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-style.scss +++ b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-style.scss @@ -19,7 +19,24 @@ /* * Conlet specific styles. */ -.jdrupes-vmoperator-vmviewer-preview { +.jdrupes-vmoperator-vmviewer { + + span[role="button"].svg-icon { + display: inline-block; + line-height: 1; + /* Align with forkawesome */ + font-size: 14px; + fill: var(--primary); + + &[aria-disabled="true"], &[aria-disabled=""] { + fill: var(--disabled); + } + + svg { + height: 2ex; + width: 1em; + } + } [role=button] { padding: 0.25rem; @@ -28,7 +45,10 @@ box-shadow: var(--darkening); } } +} +.jdrupes-vmoperator-vmviewer.jdrupes-vmoperator-vmviewer-preview { + img { height: 3em; padding: 0.25rem; @@ -37,9 +57,42 @@ opacity: 0.4; } } + + .jdrupes-vmoperator-vmviewer-preview-action-list { + white-space: nowrap; + } + + span.busy::before { + font: normal normal normal 14px/1 ForkAwesome; + font-size: 1.125em; + content: "\f1ce"; + left: 1.45em; + top: 0.7em; + color: var(--info); + position: absolute; + animation: spin 2s linear infinite; + z-index: 100; + } } -.jdrupes-vmoperator-vmviewer-preview-action-list { - white-space: nowrap; +.jdrupes-vmoperator-vmviewer.jdrupes-vmoperator-vmviewer-edit { + select { + width: 15em; + } } +.jdrupes-vmoperator-vmviewer.jdrupes-vmoperator-vmviewer-confirm-reset { + p { + text-align: center; + } + + span[role="button"].svg-icon { + fill: var(--danger); + + svg { + width: 2.5em; + height: 2.5em; + } + } + +} From bbd9d3baff5e77f0b31b4a64873db205f77ccf21 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 10 Jun 2024 15:15:00 +0200 Subject: [PATCH 142/462] Upgrade OIDC login library. --- org.jdrupes.vmoperator.manager/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index 1887108..49fbf3f 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -21,7 +21,7 @@ dependencies { implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.7.0,2)' 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.4.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconlet.oidclogin:[1.5.0,2)' implementation 'org.jgrapes:org.jgrapes.webconlet.markdowndisplay:[1.2.0,2)' runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.sysinfo:[1.4.0,2)' From 6213aa5970085ef782906bf554b60bfa9cebdd61 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 10 Jun 2024 23:09:48 +0200 Subject: [PATCH 143/462] Use better version descriptors. --- ....vmoperator.java-common-conventions.gradle | 10 +++++-- ...s.vmoperator.versioning-conventions.gradle | 28 ++++++++++--------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle index 5185041..c7419fa 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle @@ -61,15 +61,19 @@ java { jar { manifest { - inputs.property("gitDescriptor", { grgit.describe(always: true) }) + def matchExpr = [ project.tagName + "*" ] + + inputs.property("gitDescriptor", + { grgit.describe(always: true, match: matchExpr) }) // Set Git revision information in the manifests of built bundles + def gitDesc = grgit.describe(always: true, match: matchExpr) attributes([ "Implementation-Title": project.name, - "Implementation-Version": "$project.version (built from ${grgit.describe(always: true)})", + "Implementation-Version": "$project.version (built from ${gitDesc})", "Implementation-Vendor": grgit.repository.jgit.repository.config.getString("user", null, "name") + " (" + grgit.repository.jgit.repository.config.getString("user", null, "email") + ")", - "Git-Descriptor": grgit.describe(always: true), + "Git-Descriptor": gitDesc, "Git-SHA": grgit.head().id, ]) } diff --git a/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle index 40ced48..a9e8dfe 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.versioning-conventions.gradle @@ -11,22 +11,24 @@ plugins { id 'pl.allegro.tech.build.axion-release' } +def shortened = project.name.startsWith(project.group + ".") ? + project.name.substring(project.group.length() + 1) : project.name +if (shortened == "manager") { + shortened = "manager-app"; +} +var tagName = shortened.replace('.', '-') + "-" +if (grgit.branch.current.name != "main" + && grgit.branch.current.name != "HEAD" + && !grgit.branch.current.name.startsWith("release") + && !grgit.branch.current.name.startsWith("develop")) { + tagName = tagName + grgit.branch.current.name.replace('/', '-') + "-" +} +project.ext.tagName = tagName + scmVersion { versionIncrementer 'incrementMinor' tag { - def shortened = project.name.startsWith(project.group + ".") ? - project.name.substring(project.group.length() + 1) : project.name - if (shortened == "manager") { - shortened = "manager-app"; - } - var p = shortened.replace('.', '-') + "-" - if (grgit.branch.current.name != "main" - && grgit.branch.current.name != "HEAD" - && !grgit.branch.current.name.startsWith("release") - && !grgit.branch.current.name.startsWith("develop")) { - p = p + grgit.branch.current.name.replace('/', '-') + "-" - } - prefix = p + prefix = project.tagName } } project.version = scmVersion.version From d5c9a0c30219c97033e651b5a635eb80214e93c1 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 10 Jun 2024 23:10:11 +0200 Subject: [PATCH 144/462] Upgrade base library. --- org.jdrupes.vmoperator.manager/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index 49fbf3f..c90ad54 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -21,7 +21,7 @@ dependencies { implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.7.0,2)' 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.5.0,2)' + implementation 'org.jgrapes:org.jgrapes.webconlet.oidclogin:[1.6.0,2)' implementation 'org.jgrapes:org.jgrapes.webconlet.markdowndisplay:[1.2.0,2)' runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.sysinfo:[1.4.0,2)' From 9c31f574b83ea42773c8341991a7acf5150f5781 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Wed, 12 Jun 2024 20:53:18 +0200 Subject: [PATCH 145/462] Fix accessing console without password. --- .../src/org/jdrupes/vmoperator/vmviewer/VmViewer.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java index fe48d3b..cae53b0 100644 --- a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java +++ b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/VmViewer.java @@ -513,8 +513,8 @@ public class VmViewer extends FreeMarkerConlet { case "openConsole": if (perms.contains(Permission.ACCESS_CONSOLE)) { var pwQuery = Event.onCompletion(new GetDisplayPassword(vmDef), - e -> e.password().ifPresent( - pw -> openConsole(vmName, channel, model, pw))); + e -> openConsole(vmName, channel, model, + e.password().orElse(null))); fire(pwQuery, vmChannel); } break; @@ -555,7 +555,10 @@ public class VmViewer extends FreeMarkerConlet { .append("[virt-viewer]\ntype=spice\nhost=") .append(addr.get().getHostAddress()).append("\nport=") .append(Integer.toString(port.get().getAsInt())) - .append("\npassword=").append(password).append('\n'); + .append('\n'); + if (password != null) { + data.append("password=").append(password).append('\n'); + } proxyUrl.map(JsonPrimitive::getAsString).ifPresent(u -> { if (!Strings.isNullOrEmpty(u)) { data.append("proxy=").append(u).append('\n'); From 10182efea16f2a8885874be00fd10e0981fa055e Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Thu, 13 Jun 2024 22:15:33 +0200 Subject: [PATCH 146/462] Feature/use java21 (#31) Switch to using Java-21. --- .github/workflows/gradle.yml | 4 +- .github/workflows/release.yml | 4 +- ....vmoperator.java-common-conventions.gradle | 2 +- ...pes.vmoperator.java-doc-conventions.gradle | 52 +- gradle/wrapper/gradle-wrapper.jar | Bin 61608 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 31 +- gradlew.bat | 20 +- misc/DejaVuSans-Bold.woff2 | Bin 236792 -> 0 bytes misc/DejaVuSans.woff2 | Bin 257460 -> 0 bytes misc/DejaVuSansMono-Bold.woff2 | Bin 143796 -> 0 bytes misc/DejaVuSansMono.woff2 | Bin 145192 -> 0 bytes misc/DejaVuSerif-Bold.woff2 | Bin 132740 -> 0 bytes misc/DejaVuSerif.woff2 | Bin 146864 -> 0 bytes misc/javadoc-overwrites.css | 2 + misc/stylesheet.css | 904 ------------------ .../jdrupes/vmoperator/manager/Containerfile | 2 +- .../vmoperator/manager/Reconciler.java | 8 +- .../runner/qemu/CdMediaController.java | 10 +- .../runner/qemu/Containerfile.alpine | 2 +- .../vmoperator/runner/qemu/Containerfile.arch | 2 +- .../vmoperator/runner/qemu/CpuController.java | 4 +- .../runner/qemu/DisplayController.java | 4 +- .../vmoperator/runner/qemu/Runner.java | 22 +- .../vmoperator/runner/qemu/StatusUpdater.java | 16 +- .../runner/qemu/events/ConfigureQemu.java | 8 +- .../runner/qemu/events/RunnerStateChange.java | 10 +- .../runner/qemu/events/TrayMovedEvent.java | 2 +- 28 files changed, 114 insertions(+), 998 deletions(-) delete mode 100644 misc/DejaVuSans-Bold.woff2 delete mode 100644 misc/DejaVuSans.woff2 delete mode 100644 misc/DejaVuSansMono-Bold.woff2 delete mode 100644 misc/DejaVuSansMono.woff2 delete mode 100644 misc/DejaVuSerif-Bold.woff2 delete mode 100644 misc/DejaVuSerif.woff2 create mode 100644 misc/javadoc-overwrites.css delete mode 100644 misc/stylesheet.css diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 547c1a4..afb5aab 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -22,10 +22,10 @@ jobs: fetch-depth: 0 - name: Install graphviz run: sudo apt-get install graphviz - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: - java-version: '17' + java-version: '21' distribution: 'temurin' - name: Build with Gradle run: ./gradlew -Prepo.access.token=${{ secrets.REPO_ACCESS_TOKEN }} stage diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e99ee03..a9b5d98 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,10 +31,10 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: - java-version: '17' + java-version: '21' distribution: 'temurin' - name: Push with Gradle run: ./gradlew -Prepo.access.token=${{ secrets.REPO_ACCESS_TOKEN }} -Pdocker.registry=ghcr.io/${{ github.actor }} stage pushImages diff --git a/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle index c7419fa..605dc09 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.java-common-conventions.gradle @@ -55,7 +55,7 @@ sourceSets { java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(21) } } diff --git a/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle index 95d7eff..081b7d9 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle @@ -22,31 +22,28 @@ configurations { } dependencies { - markdownDoclet "org.jdrupes.mdoclet:doclet:3.1.0" - javadocTaglets "org.jdrupes.taglets:plantuml-taglet:2.1.0" -} - -task javadocResources(type: Copy) { - into file(docDestinationDir) - from ("${rootProject.rootDir}/misc") { - include '*.woff2' - } + markdownDoclet "org.jdrupes.mdoclet:doclet:4.0.0" + javadocTaglets "org.jdrupes.taglets:plantuml-taglet:3.0.0" } task apidocs (type: JavaExec) { // Does not work on JitPack, no /usr/bin/dot - enabled = JavaVersion.current() == JavaVersion.VERSION_17 - - dependsOn javadocResources + enabled = JavaVersion.current() == JavaVersion.VERSION_21 outputs.dir(docDestinationDir) inputs.file rootProject.file('overview.md') - inputs.file "${rootProject.rootDir}/misc/stylesheet.css" + inputs.file "${rootProject.rootDir}/misc/javadoc-overwrites.css" - jvmArgs = ['--add-exports=jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED', - '--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED'] - main = 'jdk.javadoc.internal.tool.Main' + jvmArgs = ['--add-exports=jdk.compiler/com.sun.tools.doclint=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', + '--add-exports=jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED', + '--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit=ALL-UNNAMED', + '--add-opens=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit.resources.releases=ALL-UNNAMED', + '-Duser.language=en', '-Duser.region=US'] + mainClass = 'jdk.javadoc.internal.tool.Main' gradle.projectsEvaluated { // Make sure that other projects' compileClasspaths are resolved @@ -69,7 +66,7 @@ task apidocs (type: JavaExec) { '-package', '-use', '-linksource', - '-link', 'https://docs.oracle.com/en/java/javase/17/docs/api/', + '-link', 'https://docs.oracle.com/en/java/javase/21/docs/api/', '-link', 'https://mnlipp.github.io/jgrapes/latest-release/javadoc/', '-link', 'https://freemarker.apache.org/docs/api/', '--add-exports', 'jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED', @@ -88,7 +85,7 @@ task apidocs (type: JavaExec) { '-bottom', rootProject.file("misc/javadoc.bottom.txt").text, '--allow-script-in-comments', '-Xdoclint:-html', - '--main-stylesheet', "${rootProject.rootDir}/misc/stylesheet.css", + '--add-stylesheet', "${rootProject.rootDir}/misc/javadoc-overwrites.css", '--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.formats.html=ALL-UNNAMED', '-quiet' ] @@ -97,6 +94,25 @@ task apidocs (type: JavaExec) { ignoreExitValue true } +task testJavadoc(type: Javadoc) { + enabled = JavaVersion.current() == JavaVersion.VERSION_21 + + source = fileTree(dir: 'testfiles', include: '**/*.java') + destinationDir = project.file("build/testfiles-gradle") + options.docletpath = configurations.markdownDoclet.files.asType(List) + options.doclet = 'org.jdrupes.mdoclet.MDoclet' + options.overview = 'testfiles/overview.md' + options.addStringOption('Xdoclint:-html', '-quiet') + + options.setJFlags([ + '--add-exports=jdk.compiler/com.sun.tools.doclint=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', + '--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED', + '--add-exports=jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED', + '--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit=ALL-UNNAMED', + '--add-opens=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit.resources.releases=ALL-UNNAMED']) +} // Prepare github authentication for plugins if (System.properties['org.ajoberstar.grgit.auth.username'] == null) { System.setProperty('org.ajoberstar.grgit.auth.username', diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7710deaf9f98673a68957ea02138b60d0a..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8707e8b..a441313 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 79a61d4..b740cf1 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..25da30d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/misc/DejaVuSans-Bold.woff2 b/misc/DejaVuSans-Bold.woff2 deleted file mode 100644 index 373095faf51a2373b8dbeb990a062d1ee5d0fbc8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 236792 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%0Djo?;{|^8Z5m*-6?wCD1DACM~{|-Mj{*d8ZOd?;9 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∈CucM^lJrPd^1yvgL^ki@#`W^H0HUJWJ+ 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⁢R|n#JIBy@$BC2~ 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

#3r2K-Rrxj@p`}Nq z;$4vFlMr|BpjVS`{-D#hx5>*KYlsc*^S*hCBM%K$BoLb zMv(FqjU-9}39vdUC*5BphEAZ1qbUuq8XI)l{UnjGb_MrNcu{XcI^EA~F0!!x62u>G zLo09dgIIH%W>#u2bV85*(!0$Y1xs5=^e=>icn}^xr;}lmtO+h752>*P#WK7bB{R-t zEMm@fmrQK(D$%u=I@X8M!|3#I#ZQ}hs|L>Ov?9MKbEa~S8?q@iQr=7JwoLE*YT1*c zC^2qlTX+IQE|0LK=&eC4tsvu0^^$LMzvQ*W_%e${la5J|AUgI(P{6a+oD}zrpS-{v zYIP=JAs#PmiFj|*?~F>C)FNF1R>DxcIN^ce>fiP>3*2vQvr(vLMb96*_cVz{vhcKI zKnfB`cOS@Y$U~^akpyXltHqGbjnS2 zpL~eCV$#|OSNx7r!h+w?5Bi>xFY|C_Zxt$R)!{P251U`@Kskl5Ef|+{NPj(Kv-zAF zQp@=@{&)nX8f}r4wC>ZXD>H|57Ncci5UAPv+hZtjD3fkfsX_*8>9tuLbAq#m=FO7wJiZ# zXf7m=VG1&GF&`V1=8Lw76eaZ;9(t20fs}y-d=*y@$QvKCI5V$+sB=lM-^t6iM^#ih z5PqlG^${xRK`H$j6H2v*B%0>Fs?Sy!do~FNXmCn`jRR_|o{DN&?Hn-QprQ^|+jrz_ z$0IYD*bqZNnL8nq#dzSVSV#aY8KffQXw2Y*T~(!IWiXQ%b(CU#I#6kqwJ=%{I3~mL zm}Fm5kFx59niFt|g(FmQWF9U^DdSYK;qU>0 zEQmUwZ>ZHXls~fwBpS3u)A9cse>dm zQ4^d0Z%oGpGh6^`npzAwU*Mw{6JuypaAjKOfx0*6fAvkR0Uh`RnW)&v&r6S{eYz{O z+zHDkS*JgN;;lkr@C>&WM%@ohCs^MvCWHMoc!L4vE zXN`*m^H~Rc9L3^kSK|a{C4~jtoU($Ax1qpvFR!%^p-E7H6D% z(8S=}koV0~4@DWvHXmkp<>+FcW

F4X6 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 G;Qs>-o2{w< diff --git a/misc/DejaVuSerif-Bold.woff2 b/misc/DejaVuSerif-Bold.woff2 deleted file mode 100644 index 655ac569759c0c53bdbefc1f352df3ee1e8bbb5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 132740 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

uT@XGwKMV=PMTr2V)&AHYnbaf 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�^ptkUWH&$ ziy)Qb+P_uN-=@_W{=aW-!o5cK=ErUn-cgx=nN2M9I-9MVOW?c%mi~a-@FwTcaqKT; zz1Wj)dHO+jLXz5xj@7#R%}o!z6wdiuqxfIf-~HYk%h1CB8Mxp<-V0oeDvv5rMVC#R zNzkIQ&IehYHP)fCKQj-~&nPdLeHY3jPkCIY{Fw*DtsyMK1$Poz)WcWdFyU{Q4vOiY zaH9%OU;{L@Gcm|=X3YZt`&K90$r6m3StmfX$i9A2j zg&)mALAq{Psg-q}&L!e2_4WJIcsh|ZoD7odwD*acjfC>=XS|AKK?LWA9VKKy~JvbC6Z_FYmZZRVxhd(d`_fUoaN~X z+g)y!&$)jOt4UtRl;uhL^WAyn45xkq?~2yenV7I=4>zSKwVSpMifLtBEi1 zoYABnUb(NCTr~AlXqSB57Ux-alJ~kp>ToG}A<89j&a*o5ZoD7oi@Bf8yRNKXom(gM zp7PG&yI_o`3#MNA_q5n8eP_IQKhF2^d^+#S*RRg)k`lcuo}Pa7PT{>S&Q$UBt8>dl zXK*d%&q`hGlh0!H?0%KspZ_POKOJBxau$VKz$`j80wiWoY#tvQLtIkb?$tAj|NRzP z0e%j6xFmsHFLW{-XZ=5h4$oa2NxfIF8qJ4EACs!*JBeJwENe4u$qAsQN-d8_L>Rao z-k(lBB%Lzg=ru7gGI4Nnar5x<@e2qF z35$q|iAzXINz2H}$t$epttCOr@q#ERWTi^2G0+<7jC{q|#MI1|?X~l7l~wNYt8r3S!AiH5drM^a^hz%kGGDXVPN6l5hAik$l;??OuE3pWNXp|PGW$E z+=>t#b^CUS-UM57m&7awp+gc!@`j~q!H^@!v=T4BE_b56lQjBNl4W9!>Ki08@Wkmy zGi{u1R!W+fl;FIcqQ~iJdIIL4%}4yuHzp=IVcN+{#Me(I7m4NTNhS1D*SS3hV7wm4 z@#nYs3t}qN0?gafG5G=1mbhHh-*Yh9{7UdTc(N-1_r%|o(1+Xv&#gKbq1gY!+q~MV ze|-Iw@HO?p0zl6@EC`^*)g|d1JL1YK->()W#1|+`1#k!od7`Aw1JHB->967LANMDf zywWzk?M;8mz?{V~d%yc*^U%GJk-INN{L6o#_$wHn>GuC`;%B+v2TSgq_hHE=0r0OA z-~KJ7y81;}cF<_)VA0%Pfo@g+jsp1MJvnp$2A%xi&Y;XTv=T>G0Jx%B{?p1n`SVDN z8l~DL{Bh`ttzDaZ(QZH&FY;Eh4*S zRfGTWheQ6_?S%kuLUIL6;fTiRth;{W_JU>;g>gpDXFh~ zSq#!O-aXRF6+`ZrYd(s>aYTxt_ZNfkgvb}*eH#0v0FnT8WICjgiR+G7((;9`j#4p+ z5R)x=Wt$;=UdQPxk|0G$uI;sTHi}}-^GBRe^drS6yJSknkD*=}q#-_1R-e>B@;!_u zj=0vcem;O98M8)!bcn>IqeJpn`3t0K9O35!mS{XN1>^+v zO3PO+U}pR%nCLiC(WoM4iQ{1E&eWD~o(o@BAa;R#r4_N5kua2zTzC=#;xRevPP+L9sN6R7s?}UGUmF#$a@aY*cTM5D!aO0L`_i<@>U`!TG(iC%C zQC7!R8O>%7bG45x@82`(yw7igaJ#*s~&Y&)(Q#*s3AftNjx8_Nazm>3N+7j_

xKz*o@s|n4KSLGCl9JH@s;F#YOQbyo;{rRoVJRWoLGujgeyi z4v!*ps>+gvMjLlWy|3cXS$GTB?*=5yAkMB!v~ArrLj2hmx*OJU8yvK+w*D@#+UjTk?XK-NMo5}g@e%F(9R5r6V@JG=gskerYC$f2b0z;YSn!TSm+bgg z5uG=5ry%AJBd;!HWs`A#sRjc{{qJ9`gk#!Ugw3bIA+MtupX`(WAn#)s;%ijonbUF~ z$=0((vy-^K)Ai<4?;fbV@_sujbvOIR`*ro4yZt9RTTbQ8{1&6T=|w+i<QZMl92iGpMKBm>Plcz9F zanLu>l2!yozmix=6ua~sAuyQ;QwJ?Pui$&@NR$|CCh6@e_M|XJQP7ucAz(SSMqG23 zaldn=x$-9IA5$Yk=5W~hUgVgLWE_qn2EIBB3a74mY|i ze=AzuIH_h<9NbO5i(rk+9G5Mp%Of(fAN@TH`p1*EyuPBf!a8UU&o#~=zd+f*uqNnd zs51!w8z|gOj3F#ZuR&!qyJ51{kzA{gLnGDPhpXT%3h8(Fh+oFBn_~cdW|xM0 z&c=~gzW5Amoni7)sJ)nf56y14qpY;wOn-j_LQ=SQOxx)xdo~#xrk+dXo#fv!*pAVm zkJd`ivPO};rYtL|f#lMM5%MjitgoWH-FZAhk1A!^PQ@jchIuZGLv1$F$2M?+Mx$^X zr`P+%$oLzlbd?=ONYSf1XxWpZH1dAC)p3G(bcecUWR=!$D^iQ?)1fb5-`5Q)dXrX1 zt6K>erKAJyn^xtZ#97G2@(i8o%<=D4%)ag%!Wc7s-9GwtSw~i3d29Oud~~>YYuJ~> zc|~8_fxKh^-6dbdT`NwuxOlO{#a*k|4S$tX^mh{XYOX{5o+)Lw=W696y#C&Wy^dS` zh#l@h(bm_owvy$$GBM&vmh$=y;Ca!g_wQqOxbd5=(!n}N@G>oV{P=M^Xw1RA{bqL; zSPuG!#x2Fi$4u}t~MjzRJ;iG>?9zq;d6CECc<6P^-%dXX{`#m7rqS0xUm@SxS#Pw2_@0!zA2<|q?U-}G zlTxsk{Q9Kjy)^L-c*mLJ<$Eh51=pQVDVNuQI&&6yd)&ixUtRALPkDJf&SCsP%jOI? ztN}&3#K-X{*seATqt(hv}KJCr*1Pecz9Hm^?wKL)Rk^;Ta<_>nQ%3mv`kXF~oe=4O_mvvCU9g z8Zg&>>_cg=SPcV)(sjWdl<3ncxWb|2fhQrI%yThAl!X|@U0ku z44wpA=HeI`xjxY(h@w+3c8&?xpq6=&^9P>Pbh6CFDYBOy#WsHM(bD6S4oOh;^S9$v z{B#$f=?^8+5Sg9!=(J%H4Upu3f+42r7e9|z0R$j8O8ak3agad4`k=@Pia&x<5|qV3 zYa}m;sJC1+5}@kcIo$^NW1(}LSXwIEq+Z6TTAN#Ao4_i9RNqLg93X&70lB9^IM+n- z=nNA>ljR77A9JSa?@CZ9mH3%rkeCg6x2`Fa4G@i-W7R8_0up*~uF`w>QEW@0MqA0V zuFW7v2CcrJB?}6zpok3efGAAtnb3bK3^*4opEf)h5QFY8vM^xp+tgy8i0IZ3U0-+%xM4_P9HAJYfT4gvo4gksfKt2gPd~j zCg(Pf?9HGIQp<3x6}c)r?P%m1Y|pzYzG(+US^U%EauZ6S`ikJuvU#*AZM-cUNObBh zo_cjwx#uH@qnht*>b=RiCt*WGEwGRKpBaBj<0YIDK|$x4+%{>XNPfEc<~E61Y${yO zNQxb_lRTT#i|3|2qiHN5-(O@KVXfyR63*SGp`D#nyD=rO(KX&B0 z`l$~b190p;wAS|J{a=a~HCvYj*S4~=^TwCSNkK=vzR;P__TQ=CLN6@m6>a+Ix!SRikLggTXf5H4>l%0im2l1J3iPx(qs%G}ttXm-7Ua zHaYbK)%&X8l*GN=ccyMm)#tc56EWrfo(IKuk;m+b1cQ}5GB1m%(Jn8sVS8RW06XIY ztS+qUyA5(uKzly*uyk|ot+H2FVJ4(V9(ZM5|B_P9p|=(@y8=~nxpyqZzpvi#5!YJp zj0W857C>0m#9otLhVLyxPp>ILJx5YL1poQ_nZ8*MFj#?^sQcq~_aChffHqwTzUeQJ zbQa|zw}B;bV37%3CXJQQXFzTEjWwq)Rcu zArTI)eoZAKfU>_UAOxDKk_!pr{FsuAl>@5Sv~zd#(%!HHZ1>-3B>TvIded35WH^w1px2r z1-W-D!{2_MKH~Q*Ac@cOpO6a!W~RAhYsHpuE#nK3?tpmohPvZbR@ONwE*t{u%1C8` zmU%IR_rAT^osZX56d4YnWUhJf_C(6YX0eNxS$V=&ez>+#qwhX?161JqS!u#{$9%o` zP1C;<3f>U8SLXNIvHxY$|FW>VWBAt%y(jF=jrGuGY}hvio!l#Sjk&??zR|ngzJKi7 zLf^v!c87fx(EXxJx2l$#ymqe?yeMqW?iPZp&B7Olx$E`aCvCmb-In(4HRz5v`^lW^ z_MR{FWxY6KK0NvR$y&E#>fft_JjJ8?cF0$h&C%O-&`SzZ8_(}fh7j4??&XS2dAK)} zxxorwQTC)Nd`FR|l`ko$Uujhi+>2gQ_SI1Io}zrT8~dW7Q)tDTie4Lu&Ku^Pk=Vbh zUYmJ8_ewL~OyPUVzMmX>pWQI<3(6+rvnTY!>Sxf~1E`aW09dy>l<>xtHqc;f-#w14 zzhjzhndLxYwV8dsyw8aNJiYAmBH#Hc>Hd-&vjWGvC{GZuX0w*$md_mJW^!p#GSQ{U zqWm-0<&IZbEb#GY;N`Lm2}xmoy!6RkNbJBzoPwHDKuOcHEPxb;U;S`pr+wLIdd_$z zU%td9%k`q~m~7_|osQAA>=){R%BKs1MQjvkllHl5RgWfG*e%#TFRReuPc?N;);!@L zumlVWxm>dAoP4%COuXK`>?5idbz1l8ktc^MM<5K=iWfjtI&HO>#Is!ZSkALZhXE(* z$N`cKUA6YhB1;n)ia=3q%YEv(PAe%mLkK3@QAJM+LR@;~w!vio8uMv0o&MOmhfUWP zqf0_87x|*{F;lF@hRcSnY-jg%OwN86rPj zl}5~`)@_C;rU=#Yw~ZgiU{7#}%jtTQN~1K2lX%d3WhTBdgIBtVS7ycJ#Syu3dDLoe zTXBi3ID^(m?9fvvsuJvA8dL{l2-s6C$M-#K8v6GletxI=dm5sgmZR}V;bT3DXWQz0 z%Bx1TcTSz~Mxz=`Mh&Q5nYNrt^je$F4qe2r3d}3xrcUQzIaLl$J7x99ig)U*6u^bE5`DOv8_oA7oL-@!1p&~hukEq4BRz`!BLd&o+c%i=}v znFtN@g`J^4=nx9z}1stxX>I6;6o^vjpB-41i*h|kLbj~JBjwbOm+4JPwUE+NB z__R4$pN1}zmA?|OW`MF(GNhPDp4NlZ3onRZO6V1B4z~P_+Jr>|DK%LwO~8O=Dj5EW z;X1&amW(niu&&UM@KBbqK`jcXAg2&{(gM%0+RIGK3asG6sM>gDr_nPK02A}926`ld zM9}03$1zYwI2F4V>aNb2;1sh68|H z4rK*kP`WJ5ffvJo{3h~(m;(qGIG8x zUx4ZerjAh9>?p2iI*-^a$)y3BT4K@pj#i<&4lNEvD|2Ts2?7KtA+Xfp)6yY`5(Jik zA+&7_iIJS0`%5eXZWD0ws4;$Iq>q?u(DR@r@Su<+k|J$MIEZGYNK6etzEUNj3n~kv zhoL4!%xdE(mX3-v0oQ9crJcN8;1J0O$|7NU(87FS;N(wP~=FVC`m!f8d-%^xFR7!_)=XcjAFUp zsv$t>Ku~~yve54W5AacpL6I@iJ;FEjMuIln8N&^v0*)(#)w#xHjjA@$V3F}!ZMNx| zvT#b^W1%sm1&(W?>t}eLC)J`*eA(;>*v6_@zu71S`B=_V( z+tS`;qwnPECf+Kfcj^u{iw)f=J4zD_%)GwEjDUq2STMU*n~`x$fY>vA^sI;5q3kqk@R4BSi^LJM~5Aj%e$6$k~60!HfdKmSg(w88> zV>>a%7V~}?WUHc2Z?y2{@bZI1VnP~DdZ-M2a-a7fV;26LQueJx)mY`?i zLIkt~PWtwc@)YITErW?N38vvXyby}YAvt{~7N<>M08kD|I2ZACC>*iq!;a@wrY>Msyplthr{tin)0ix%B#VgU9&Y; z^R-Zmi|LxpEORfIUeRMc-V1*4e_%iG-@=Re#VbFU$kYLnG!YHdp$oK2Z$`({MKqh5 z=tZp`l9g4WA{V2`V8CW)Y!Ojt5A|U{_@%5nrfbWJD)!Ww;;F0Ye%#7)rX_P>S+Xt- zQ&eR+?m;jUmNx8_ux<>D$H*a2al>Q-i!6NE6E5=Q+R z2n`x3VpbIw#FGZCUogCSQ%E0IV|L6v?WBK5MQ@a1;8jk1w5FrlA8nhsMym;F{z)cB@+NfwlcUvRO&QR|VO@zdrhr;-Z5!IyHkD%9*C& z($ZiIweiNeyBppJ$JewzigHWSacF@CR!*Hm&j~*Cdbn!E0qteDX5}P}WW$2jfOyxm z1Ra?p4y&Lae1JD>N{%xigdG0aN-@&I)vDA~Ta6Ms(^Ac=(!qA=;H`!hD(~Yh7!2Q+ zw1jn~O9y!eZ|9a|)YO?(itSd;0XaXYK?P|E&{sS{eM&Qik~8JxDAkjR9{$$4dW*;z zrMdCMZciSaI(@_Gd|bMfrK;)wa5n^R{S9PkGuio>dQ5En<{0K#lKe2z}(%4NXu__ZFDZ9#bM` z4YrbCN~q!pVw_E_^v5|Ik?g4srXNjvX^>_wLvFQGXs5P={wg!1fXQ0p z@NqhVhz7b&>9bAG7z-gD6QSvtLU0upsHO%Jq~jYwsfkf&VMJI zLDdIOW}fE@s*-5{0;yDzdBnWw55Oa;7qNGV%7lBwaLyQ`o<<+Hn?a`0M zBUsAeHFHMCtu{AOL$B%GLXU+Soei>}$_+uR2$)(al1}8&u%mR`u2<9Af1YxT9>e3| zM65Q`%DkTo7n{Q8G150n-$i{w`(;s6!DaYFdV&FkdUU8lo$SCzZ60`k7_P zrSME(WY%L;PW-Yi<}SAurLkZ@gs_rP0%3r%#h6pIaY;a^eknD^@!Ao+4sdS(+MdoS zM-g0deJkJ!H@I7nG)PmLca$L+(xKmHeLf6=%b=>Dt;T7*HfXcU#vtoounQ>nIXB@8 z^n$+=?DM}-HHMGG4Ur?8jajy*S%hc9+mH2D=wJXw4XFI0(Ra6jiO>xSF~y-v-70*cc}gwG zCAHud{91F_ysVzo4cG7t7`ZB$ysDVGs;@idu6yQbNdp>g;Vu706X3uP@M0L6N~x%X z&Mc&s4r26q;ph$^iYA*FvyLHCgEL`9MBN9sS61ehU)DvMcNQTOMP$Zj;*y5Ck@OaZO1>tGot3Y8u z&mlO<-xa_JGHv`_n6_dRA+2^91NJi5&UHXZLr83RiL!t>R^J*MJT1P)x#! zd^KjC1s2(iRjk39s;@CTt%A_~sQ9Fn_@oCn=)p;#%FpQoXP>JJ;cFC}Yr0`ZgNvk* zmzW1F1MBGgudhl9hLb=ZL#;T*%Fp6A&HSSr7&FQ#5Zo9~GQ-n`pvZ0Q*B$<3l{=|C z_pOR1KCu@$1=L%(`{se1(L(yIc5=Q>P)`)Sy6)8|_!$Nff=S>+86S0Ds^m}OOPwFk z-0;DHwM51Pa+Yritqk|=s>DfSY&{&!dclg#;;;8E{U`jV2xOoF9hko$jF6#1hgq|3 zxH?h@n0=za&)G}ko}6iEPXf`npY~0;N z@@&$P3P)N|%_Vuwo$x?q;u7$O_%K>Xz1~=UBfK_3p}Mi%3{cGpHlhf@#eqb1<-muB zT`D*>*){SR_PJpaHmkxfob`AT9sVf}Nf=sXs5vF!Za@M@pfE$R9XmNMc^;HODoT-u zWjaY$Dc8Gu!o(BhF|eWV08kSi=!b@thCc2;wKH*k)3)ulJ?5)?!d*?A0x+4~lYW+d zd<%W#L?H?sU2gWfbqs+aw=cT*ow2;Y)DU;Z25(**#Isw21pBFt;Y z5+OdD0a+$k8GBo67hIgo*0cJI&e}8a+*t;SnHB%I?O%N`yeqIC{>o5YOlY2c_RhU> z92`6GcJ^Z__XBYO9gta1+cjS151L%Z^%xDQLp_zRoy4M+@F%OGYc< zsh0q~g~=2zyu+bZvq?N=H!QgPgN`$-olKHda#ymJu;fd`TRe?M_wnsKvZ=Bk-wIOV zr(VCrV_!;yWBB69)Uo)8`7xBTVd04?=bD;}AFYA9EAHZ0n0-QYfwX^3xT?Q{kN4JZ zELGL1NV?fzke_$*_cxp%qZIuLi&-o|%xGUf5JMV*L=CuXL+@==TFwO<_n;P8jTEp}e#1jN}M)WKzmkRd-f!O^$6VViH z1>-9uL|~oUg6JGi@zHoKX0fu4QQdLn%*QPy`Q0Z5@)jm8hCEdl;5I)=sBMgPP;je5miS%O=p)!dTX3aY|2kZvLmzq4}18FjHWTFDgZ z1MA-V-wgw)QBUA`-9BaSDNrb@rQz1N7I#1mYi&RWelOTlcHyqJ#LtK0~ z+#{;9Xl?+vcy1(q9)TFk+a_67!bn5Tvq%pu5i5ZZ_cXy>;bvV^0ZYJP00JnqNQ0}VY4=gKSE;&$wSsyR3{bH)2~Jr(b@e6KCxNqp;NfxY3*6_F2rz) zkWEOxCH97;l3=fvS*#|YQ&N{%!<;wd$-(kSza@Z1PtuD#Ix{;6FirYhfd0c@r^R6r zW1^-`lo5^Z!UCM^=T0*Hr0|AzmRk`~_L&AI&4^Oqu=m@ngE03D%wE7_x=-EMJBx(m z3QaI5wKsR<&1E)^0GT9l(8B_Wx`%$!E*4@!?GxWMX&wL-^>FA+{(%l-yoi<3cA-;D zxNbFay-Mie}k4=S5lrbl;meoGs=@E;# zD`mOE?!{ByXZ^T{sJw{csPJgV{(n%MGY8w4RG(vPmn^k5L;P=BXfqX*0jCi6b?9X~ za08i`2U;H03G)e@sF+zrVU~n-Bae{gLC_NtATWUMQQ%Bk$UBXR7-zN_!ZwWN9RmJ; zI2Tomvs)Pg5t7Czp_KXo;{g{VcjVP!E$4Va7If}k;HzLI_M=PWOgicxXY;t>8-Z0! z+$OhEUVDV&EiVAYK+p>F%C%Z()9>d``$OuW`_TUW{Z?D8I}hEof8UqtfA*cbhkMP> zUJ06m!?pi#cDvZHxii)>MITqkX+l4S`ITF5s@6M}9mjI3-SND;yk);+G4&BBJtVS^ zr1C@Oqw9yjY^*FFN@oX0=>MV^3(5LSIZ>F6hzm6YVOV>yJuU;TqEQ5355ZAHuw}o2 z#jikuDqe6jt=(UMsR1DK(dRw_)`nCwc>w1haMw45=&jx=#)yMQSi*n=|Z8*Z2BmvUm zzYbQw;E2kj2}QL`LeP#=iEx!;dDXQY4RTfHFfs?!;>~PMu$y2Y0U@B?1Fg-k@d?-a zaw1a~W?Xnv`{%LgYJZO6`**|1n$N}l_6xSxt3E0b;Lr*O)}d;?C?2dAsfS%=$;a+` zbZ9+x%cH~Py%`>A+J8yHX+a=SY4Ll?($=XK#V^YwU{Bz?{0fOld;Qeh+nV-S2JAiD z1ZLoGrs)Ta=mR8YSrHq9&AuI5DT|PZFU7fRzp})#?Un#%)?kiuAKhJmjU+DaN_dl87 zLoGrEX@8>q2_ZsYl#!@yj5_uZoq>J(!e#LG%Y;mi9Y}^Bw;+rp%T5Acy%lcTv$O-J zxR_mCUFr+rQ2(%sY7wv_A%1>!Q_LQd3_nZ!d}{HCWj1pw-j1~{`QBGWsSCgrH)zuO8n^@^oy z=*_(Xf2-LieQ%zgxjw3VpMnGIm2~5~&5s%2t|GrZ)PBFroEcN~jAX8rrTJTn=msSO zrw9ji)kx$jf{;$L*Q~Rmwevzp4v?RGN&93lXbGngvyOg8FDxbP6spi~^1FSz+fTE! zS2DfXD=W1z68Za*bi9@SpA z-O>lFkv3suklgQ;5QsJhEXM<(4eJJeQeKkPNhH5|eAwW|=VsYW<39pcb}>!~3D!`} z15YjXKZy8UQrbY_on`o)BECs)X>oJgRZNQB$7z%D1v!oOk7IKV)D2hr?_lD< z_swf_NZZnE*X~GC5%4HxNDYvJKeIv64TX&oB9re! zB|FYSZp9Oq4}@>kZvMSqqWa6VkbR6D0|A6z6t<;M#{v|jxE`F$o^YH5QFp`Lc~b%U)o+Si$uVUm^2x{ zlel?|hmH;ElCRmah5>J*A_ov-oxx!gx6$wbUT7bn*%ms8i^B$A{0Y-V0f8Vym#xXw ztLsU9g1P->vM7N%c5gP>lfLDtb+Q8wz=!0}G?6#Z4Q<8vS!!V)?&f0gL0IyUe!WkT%_YtL zbu|L&lOKvJ+uDP#wcJf?6wC}gJy=+A}R+T zLI7hTzfV|=zIxajBK}SA`&-uqi_L_7PQa?}Xwx4ats~v@oS~4V@0h)IijL9*J!D&F z4X_uOvA_Zgiff}QFEuxw822Gy+3ti}|E;xg${s?1**H`_MoDEzlA;{AAUW^==?`Y( zEFfv%^Tr+npSr4ZUB4XK;c!6JBJ$!%hO1~HZ*jsAE({iyRw0IwlbUQRx|O= zq@y%0UIsT#@1mwXJwt%AZBG>wi!VD1a$?{T$^4vR%pobCCf(FC~u4qFd~tYnZ6^r zY`eB4uq;SLAY3uw=qd2cLpy{jej<2{ztS6_QHh6N=)i^XC}4UjT*me|3V#@>WyT*n z&{}XBMFO0s3**>|a{t$D|Blukep9aA3;>0#up*$R4VZ)IpL~@F0qnSkBw9u~P93dt zco8A9$vrXw1w%~NG5zJqW+JtQ?qx)vV~#svq)|p26KxiDk1@m;6Y-I-LtEOB9S4R= zgg(Z#>qjXj3393AhL&d|^_j41&w8N(wT7|J=Wow4g!%=*k4{|)xE3)2_;Iy)Rf7DK-u~YUTeNC8&s; zW`YR(6W6T>)TNk+wE!%$t`&G4Cjx$5I1FR%)u^cJUCeg`N$|F&1<5n9WxL0xhxOue zWc72mTgyhbnN$*Zc@+|7$5b&J&0;4wih7>48`nIy=eQKhPh?$?cLu#z8=*65A_o#1 z*_mB4szLv~KyNcLapBf_Lo8a38ytSYbwpEr@qE6=XbhcSh)yieUZTu*dsgi^i_B1R z8OCX)X`QAim007ZcF$!ULZ*j~bCQ&9SSpeX$hO0CzZd&b zwiu%9NHh1U%oOZHg*tDoTL3*2=z={~f9)ADJKh~3iJZ{9#Q**-|O>!7}5!xz# z!;q;Z?iL-#wVoh8JT^nDWz}vXY>&`|{Y{@>-HiA)mPqBJKu4G^tMZX?ve;RpdYW-_ zF)saZ&M7H%?@~x)Pi?0td>YA!N7ByUb(ubM)nnJ%`#;W_fu5P@Z<{V%I1N}wc{}jH zf+i7bpXRJsiI0K&3t-m|x#Os;Bi7;PT^ zN$jW9Ff?||Gd8F>?Kw3wl!Z-jC*qq{yb9g5l8NgvKYS3^ncJ974JSX;Y%^-#ZPqw2 zlb#hbzlSV>+RXq@k0gvJK8r$mN6RG~l73{oEaJ^xU{VMPfMcaL z(87{qQ72l>sjKx7)0KINN*AIYWTaH&uy)QG@)@B zl`>O6KNXZus(2Djr1p|kj!n+;I4H+?t@gBh@a*^sxeCoirz=!w0l+e*M?#PIA^!tz z*q|(bUqoD3K^yC2AuYQZs^~}dN}Q zHnoM`?C)XoFSMjJT_$^MOD~Rf|4QDH6lUImRoN@Y!3h}K!l}Vmjao`}U%SM6Ag=wO z$Ms2x8?X93)h7jR7(t-#puaK8vv4UlMGV7%Lnm+A)jt!O&Bkd__Nt^rX<1dsOv*Fq z<&Mv?SKVKgGn)Nr;}zoj)EU1yi@5_c@DGNFMnW4^}1e1j`P5Yy`Q zI#=o2Uizzuw*@Mqs`pw#I8 zyAb~)7XCfe#eaQus}%t_g{&0~=xJ%A+`JW%p#@iAYP18$_A$MpZxIZcdTj*&lHa0x&)($aI1G#~s4 z6+FQpSpU~!?D}Deh1bE-GtU5=-%2*HyL0sm$6JyHI-{q2;1 zy@(mVxwL+>s0>1SIn$b}@f7`6J3RNW@7t4p=eAc@zqS0lq1oSTA8S0SjnJnwB^g;! zPc|QSGP$!ijby^d#a5axEixv}R+nh&bwU_{Ovu;;WXTzq~a zEs6wan*58P1vaGMg1MRGH#2ZEi?6LL&vl9+Rhmbrl$$Q4m(fpuNB^rkJQt=|f-`O; z+$4fvps0-xoK0^06~H7G2Yt&;Eh;J*Fj8A)&}!1{K4*YpLW!=xk+C=x#Xr)~aj@pR z8Q%vGR5CIBJ**OpR~(Md+Pwm_E{FllTwr8#vYYuZDS$_T8rqv$uW0L36B)pXTxCfc zV%kXtY%&Q4)W6`WMZ*COwcsimE-Bq6tg+p3mptvYPrx@^6a$|szfR@9KlUqd0R94m zwFDp%6g~3F>Q!=!T~4{;nl~Q--|!xwUJVkj6d6wWLV0Fw?aG7@Gcy1Hpq>zqzb$_6;b z3Jm$+r7KEC@rY7c2yaBCvL^&=VJ2+Y!FjSInJxs!VzF36L_!F`_AC~APD92yWT5vX z@R0uhIJhj7_DFjPiBQ@yv{gXolUq+to%ZO52uS2v)_+uIQ>p}Mr>8^FIp>^n&N=73 zbDovlumSEy%aFUt){8fzmMR!1Lo`0QS(DAOQ`by4oyY5W!_rar8h@`WPIl&G6J*Oz zep(^TfF(m}=>*1VFEI@EFTlhPWIoCE!!Xj;B1B ze6ad)8`FgBLTXVE|zVIX>f`F@xu2vwX6qT3TOF<#YX-2MC z@{6saW!G6sz%YyuLNE-&NH2tBjJ~4D{;n9Jm6%PUG7-&Ts4~hXR4wAT0vc4cMs_@- z!2Ii04QNHrG?2}`WmFx@wkW)C4esvl5ZpbuYjAh>;1Jy1-6goY2Y0tXkf6aG9wdA3 z+;i`D?)~0)@5k%0Mz1-ltE*;Bui0H)VTxcU)80V*F1GN6Vr6X+npX3IZIJH{0qDi4 zIR{@$hbr0q91;*hPtR_(KV#!EDOrg04QayMajNSd%1<$9r{S40YqYd!y7{AHVM`^n zsg#*W)eGLhKNT89Fzr$yH(Qxy#2rTX#LdR}p^-{*-5V9DE3a6hm1_?Oy&K}dqkN~{ zFsFC4m@d;?eE4xbt)qk2ApFPS;bHB##>Q;a=O1E7E0MHG>iP5|GRbg9oC&RpNG<>_ zdYc;Rj4SsmRQ@epnZ(@j=)u42+~1@^FL(XE1$~Uie@%r#*H4MxKO@psPUlWODC`A zNJ*IYYn_Px%4DPBY??uq+tm`a0FMLPrw3UiNB1GgsH6Tt5f5)be@V2@ z&eWQlkG7AJmP%kl886yWH81Bn3Y_+dH7P!WJXI!=8lph1hXo2o6jiT;%S_~jmnDQm zUUrsTs61coJlR)kn7aXzF?8eOM21g>aj3P5JbHt!H0jirxFwS-0D#Y!ajmaK^)UZ_ zabHT?ktpAt+@~dt=zWZtY#7IB`8BzB0=(iM`*O*abCo|?y7`%g$ylF7eoqgtJgk!g z^)N6(oOY4T2(!B+00Zwz^|g%uA$IV=`CVxm8qM4>Cgi}rm2#boTjG;7Xjzw#7Cha$ zX3FiV*hqPngW7g(q$ADu4eDb)B{rDZU4{uLx0tv)iSp98^dczY?uoPtj3N}>AMj71 zIU&pS8dHsQK8wj@gQA>}tb^-}{kB(@$)0CUFvFd;^r5-1;Qqe36ro(>vCkBl_R{s# zrO6wUU7rvUfN#$*ytvIs;1RqT)@wd6Qz1PgDjRJil%(jT5DejD1y!4h;g}7IsUbr^ zbzEn&^Sg`Rsc*Ib#?|oOf5GI)%_EBpv^sr;B=Jd-;zir0HLK#{80WQVJn|^1cqr35 z9D{sv`#A%wg8Xd*EMVVICo4h(<#}!qCE}N|SpR5}iQk-{wNm1MT_9 zIcoC!NMn)N(W}U~MzPgV=G%!+eo_ctS^f&-VPrAwZeh_{Pxj=-HsyP~2&F7LX+aEm zKDKx(MRA8w3ZJ|$LWH-d)M~}>6-wH`mVFG(Q@+P<+t3-?5jHttC;!o0{bL;YD$H|g z`!`d82?x=GV!khN2ArCsc}38-CqKu=$Skf>sXb;ZQcflsTn@V*v!W9BH2ZDYiX!?| zT4%UdXAG$g0)_ap&w%qm?7u|uoqxz!h9UpiyfQ75+=%-1AZ{%rYFryDvTdNt>EnI` z@_gq5W_+=$G&>A+yh5G1e@{-jbSTm;Ds%AN}-m5Z5qeM?=jl z0ffWA%c(~$KtJ9Vm^f@T%v6V!y+$8N6Y7Jw1XFBM)vQE22`TMUY=T)zf}6d%x>==y zb+pDd^GY@$%9$YCjWNOF5q&!+rxlDf7(md)U#3Q$Bei=;W_LyjY)~TU* zYyg{gp^5ffk^_z@LW$okqmXV_=2l`^`Q#aXg0^ID^h8(?!CyXQ4XphGDwoH>q^ zJJBeW-=-?JcXZAg%v~XdZarB)qHFFAuJvdm1F(B_5#jhL=nCLy`~ny=dXp9xyP1m$sbbcFl9k2 zq_q%+QE23zNJNgPawWx~4du82jO$~)cH%4x1^{74gzopwH3db8w-zutLpqpH$AU>L zJ4-%=eZ3f^iAOfkGSAKzs+l8xL6ya4V zB!i}&>PL~KW0$AZmhOckI16V6vW)?5=Ep3wm;G#uy+nf0a{)SZDB$j>e6LRj@xB!0 zFkdXlB65R)Lo-Rl14A7Ba}0zvw`Z75!KwB&bY+r0%L+P~fexEzz)<Z+k^knSt;*_+G;P=OrDz15Sq|`1}Aw)Pd2p?94DOda)I@?t> zyinU)bhpgG;#xI6f3P?|6W$9R$q*bQl>uGb*N;}iC8;SWV#Ly01=?T?P^Z%7kC&q! zB7(V#&IEouqG7DWAQ6=p$DsVogre7lAy1fs78MGM!3Q49AHeKGh-Q!cV2|wr4#RVK zNC8X$z8>LW-|;n65H5Bo;LzC2LM;OozG%S}ciNVxG#T!7=K)O<(DC5>bOPit8VHiU zN;D4l?&9N`xn|C(LcEXkZ3i_g)5(D;+=EK0sK`?>v(wFm;WyUq7w8+PMWar07$xrH zd|=f@AO*QRmVugMkhy!ZUm9-c-0_gEWkjUDDKhn6%@2B*x;nfg#$fLIaZNDax1`C> z#+#0x?xQg;Vy9zFM3`p7DmxgYRmO>QI=6Ui#TkHA-CAZ$h2tndYu4=&U{KcMTEeVo zW~sC4slGM- z6ppJIn35P3Io2jBXhs&}6FEjLtvXaQP>#H1x&uX2PPO9UGro`8xoyL52?rKgW5jP3B&3;- zD>c=s-WZ=5$9qsDTK#0HY%58cMCpl^HwYpzDJ45cT2R}R&2V%(PQ}3cK-9O0lQv2d z=E8IgH$k(=0{ggwEcxoiJhsY|L#OzJO4A^kQ=`GjOev4p5EILoMLPQ1$RFgJ5s7vd zV8fD3U@yD+4vD3vk^&saXYP^J2@kq_*N3%Xnhh;|+fPea-q-BR&6&(x>~xgj$Z4Tu zbHZ&lROiv7Y3&`@;tY#An=eDc)Mt<(3TV5v^fq+G2cG!UZCxx(lH#w(GALhEC8BL53}7y(LMv+yrp;DHmpkVI5( z>Ir?E@ED+Q{n{Ai0xBGcU5qu*5n%T3gfZsr!Az-W`3bf ztidj!P7L14flMW1N3YMiVfZ}7dICuvp!Ov*oHkQUASTqZKg5K4mIf4>rnw$vRn8T+ zRMYbd78-RHfq;NZB#ri$w0dwaZXwg3NESja? znJ~A)s-Dqtji7I%eb@Ply#=G8Cqy*j%z@J?BG6ZYM@tZet;LwPjdhEp@t(ZZsD4z_ zakl6;uOEdNWb$ZoIHzZ?|19COeS0@x^^Rd?(3J4exVm(LaC0+AjfKH8m^_1M|5)4b zn~o)hqj~434D){SWK`JCh=hj)1=b?Jxl>c5ZFw+@SYLztM|Q*r%$-crH8Bf+^K|bE zv#rQHii$D3?U?>Yl|{I+m=i1~2B`O&TA#DZgwd)cukvKBLOK--vGufP*cS)Pds=ll zzALTD^mD`|TTHvf|8kE%IU&pFoDphV11e zx7mj8!$d5}THR*SKq$@uL36c?_e*fnTO|S#1zE+`inYIBfJk+*v5)F0*<#1im3^u< zWLd1$wk@YBB&ld{WYyn91qnzK&}QrJ`i3dghmqd_h0Qw9A3E0_Ph=Ex9G#?n-6VR# z#z-2(__)AY-4ljyp}JnSQ>r$!qe;195JTcld+nRKUv_rP5qQRZmP8XnpHZZwo?cnK z(e_@eqiE`ajnj`WJjB{%7t5GrIF5_N&Ev*~p+dt8k>2=2*y+LxmC8V) zv3!yavA{u(;KZJTwM3Yj%rmX<@Lm#$ph_0Q`eCk?_6y5b6E>njdCWULcs9FYY(pzK zYZ?4q{I>rUO#rw+ObE(O2n5*ucu!K*F=f?e9`+zH&v3`qkb zf^cN_HFT$!g7l>rj664%WF%VRNhJApkk@?;A!3!gSe}hc7FW>{>2&qrRUi#{jkTPr z@XWvI@8@<~QY3`B5TkUcs7WOX3aF?K0&VT67`V08UIAP9GC90Q%J|U#lNe~|q?8gI z@w`cbIh!Z^%$_niiMPbl2opig-&v4-hZ^I6)gbX)@@2p_6epL%jnf0AA(l*@EHhMw zw%y&v=&+PlX1lVx5QBtLlm2ddrJ>UpZLbY2!{d{3sDATJ86KE}iit6*2peI2sLfIE z#My~irTfTONcsaqkyqW?=Vw=R+k0NngY2s{Ai@Tc?1y$($+o@~K?G8S<;@G=`%M|5 z@8bDL@ApOu$56HH&$%MOuGR&)(#^#3O5#0gH+43G7g!S+mwLin1#K`{p~>7fI98c% z$A)Yb(+NqZ@dBa;PP>+ePw||VoMM1hngGV%f~bRxb}bJnXHUv1W)DZ!In49CR2L?m zc*7qo;0G-?fk%fq#_qt}+1dnR7oNRC>U5KwGs;AyKSk~UiwDtAyUbINFUce}70rFj zekW8r?n0dD9&M#&$y5`(I6c>a2?55!QabZO z#GnY=N>N1(Y(Z27m7)PwLA1|P{LN(4lbhdoq@&Oz4*f<@^jp-ZSl7Jd@#C z0%`Nmchy@ zpg5k(So)y&mBCY+-HFZ*B(RBbtSmBKtoYXpo#oLmJNeM{0onHmU3N^@#FQiP6S6lLTF1Qm|-bsr=pq)I--?g ztU!I_<7&tC<@@=iA%WSU%ACSyC4c!5x(iiQht-d^p?b*ZBPCNo-wYwIRtEQu27yG7 z@A5cKQ)%3jklvTI6xa*A2X&ZsF*u!yPv=i%Hd>Mgs;zYF>A8&`20Z`rH^pZTN)I ze`M~I2wzFqa2lPUWN5-xHpzoBv94Q9Y&plu8(V^&p+bO?4 zhJ?zMr0h=W2qZTB?a*cWV)8Yo3xo;GRy658w?#_fR0si#WSmr-*#il2 zc1oALgcI#ax3$UqhbbJZK0Y9QJHSuj8B-=%QhRqjvSS;xv_X?`W9}KPjMCs4wU?Ne zz}qG}%9i3*hOdTP?@OZ#$-Fmpkm>_N&fBMG30_h#t`T(&Dv_odgi*C(Sc}hHa z+UzWK!ww?P{Js0Q_hw$MUCh{qu<_6cAywsv_OO=a5BaVX1Fiwp6E65T*AESV?`k?g zcA*weLN6%ay$iQwJwHA9F@tNYvJT!`FO5J;gYseZ5vn~rKl1#ZE>#J-n*cgwQ74~6LWiaGOh!~GM#VVhx zaiD5MGxSN@>I`I1bKkArFg|(P(awZQt_!NZ^Ag*thGJ1Yj_wyK6ZEg}79OAd!MC@9 zB$@#crk{N(ct3qFr(4L#!f0?+$Q?@V%Zt-zP-JC;2iL)!Y5j_Ho3cK6;Rmfl-AaO^ z&8$~Zi^t9EY&|6up3YIV1YR|=)7%0l1+47pm{CTW*+Mr@qg??zY zm?sT2;O2x1W}MX>=bS{&08J;cn`oJqKUBkg(6LMUwXix%cdUk7mZ9d;VnesjnuDcU zxNgk0@1>1%oaHc%CB+E&;tJe`IBEw^tlrY<0B-VLZkr|*?qCKbwzVpZVjl)gSQ-ey zxL(w?8=dzS!o$nV=pwfwP3B&Pb;{Ml3sOJ&aF*4xz@t*_3A_l3P$$Zn(!IxvXdg2S zOf!Bvta8{AmOE~0(6`h_+X9`62yH}{dLY@`4_{RI^e}U-Utq{TZ$W4eQ*9H5Tx*{y zgS)On8|IM!=lZ1G8?xRHZF{m7=C64AXhV93;%i_-MApu;DjMGpY%9!BTaTdxpOI6h zYZ^B9(jCjNZa&jYe&9~#I|SqR`T7D}TKEDez~WDMUs_eWH0*$)1&I(#R}iS;gklGm z$Tw(5s292jQFEPFmyTyiMPq@rkZT$iX~em3!^2j^2d?L8S?|?yJm{>8euJs~X!Vli zr5I&{#Z+T|zH&CAnCn!__rm-0wk@PfuGnK)BCWv4a3?j$Zwc+V;b_rebFt)N8!3yn zC_6tEH{64I^O7W z@v0~@zi%X|uv)KmJ%Yml;BFH7M^*~s#g|NQ zR~Q!bv6(usBj9;e@ifU6O{Mizp8>FZkc^Cc&pHTm9l|xy5r^MHR{GZ=uS^vo^L74Q zwQb}8*7t_v(Ze|k%Tc2i z5cAr8>hgltel=^v36{1>f{zJEMw+7UJ4>_vy0*EPo0{X#Q&K)sSY3{s**N!8tFjcQ z+Caic9H>RgEh=-Rh#81+r3pk0OPqZbVrR50n^+3ONR0={sUHu|RuHU08^HIOT8U1z0z-n_Rxi#{7_^=68IoYbg-;W*}#VIfhHi81GH ziIJarxE8z8y0!pW>`=w6${TTKrR2HF;q2T#`O1-~SkY=7PEJ}<+b7xK)bo-dWXW@p zoGi5yc`Km0EqwM+@MRzrsBSp_3{4O_B(3>dPSiD5x;=BXPQ$AC4KHV7iJ-$cDzv8N z+X|+w%*QRu6{(4L(JfU^__>V*OQ;DUuJi+yI+0m-_HyI=Hee>N&fsu-o94*>yT6a z>mZf+yNLg;L%m5TLt9AHxVaL4xYA=0Mro`rVC?>cWc?sRoU)*PzJ(Fn02TmHv#4-- zd&GIe%d3X?cEl0?`eS}O!h&B9aU{n-NB{uRbd7H19FBAvNk`YNp3mp(2#7J)=i|J5 zRb{2-v9%+Aqu>KqQu^0{l-29&0PyJr@&N&KaqwkvxrMH$Hh%fi`2wp`caZ};LAyG1 zC(381yKj8ca{t#5C7|D0a$S{qkwR(+$xCBh-Y?Yr`mt_G6npLU#Ae02c1~R0d1?6k zgqQu@=SxxH`BwW+vs@FFDd9}AR1}J0(|wHOm?Q^vHPhH$?Frtq%YrXm3ZubZ%uH?y z`6M5ZKAdwB1|ZNl8hs5*t7ASYIM;jBl;v!yMnLRRmJYTk^!FD6!hwJYnjAC50gme; z{Um{a5EFeWu~-2>eTSRp2+0aZhsO&QsIm2T^bM~fp!n}ZG!v>CiL z6hkZqB(sT#5OdDz<#O%2vuNkDy?Ca^?#vyo4hn<_#S!W67y3EUH)V5k>l-ox5~5;( zXa@LLfIs(uEY3M6Cz8S0o`c^ige#~p$@h)ZKCXvo9y9|+G=rGhCXBEJ7*5omkip_Z zVcoNJCx}GAA%e1EpY@XAN9FSkv}Mtq>4b2`Ok3sFV66yfSUgeQ?diTaaN|ymATV~A z(sXEB67X(kZAfRoE?a(D3upjkGC&m7V?q<)7Z#KmiY67y1g?d;ps7l(hWvY^14OfC zlTvCj>_fLH;l?a^m}5kn0gVr2pQ`qn`SUl9E=x4f6a_5*mTUrdeoe| z{GLM1(N^!{^=^h{|5|%mZF`Fe{Wk8`Jz`win6bulu+j%5l(A12GU85^TsQuj^d|Ue zA2F?Yr@b&Y(=<<<(E>m)SfMCd@2AuI5l8LI#91C>aLO;F3+I`VekSPFouv1gEb?ib zeXA{{&my|7FjDpg*?LaKx1H@AfH>y8Gv|ZKihukG2Ia)KdFIj6TrlXh611V!drMXx zeDs2M5vG#wuQRq&i77XeM5N^zBNTq{k=HPb^$Gp7xE0=8=iYSzWJBlN$3&Z6c=ejw zGzfnAlhDRTumO8HZSV3-skj81`HFEvJkytucIC&_B%+iBFN_?~9n zB}beF?9s-ewOonHwlPBE{dbXPnw`y3C(RSYah_iRM2I4^wG0YvCjAB4-7hXi9Y{aB zrj$WCen_!=-I?DX2Vn>dGpGrR;c32yzDDi!L z;f`t&aW%8NOee12>end z99@DS7EH3Y?0e6i-|?-aEVkp$s=LDFkPBCU9%vwhlUaWYmmvi^H}8Zofn*LIb20eK zJz?Y=LH@oY;%tU_o#{CGxLbvSo=R6bi`ZieQBAVyIvA@R3-9Y%B(+Mutr_;35gdNZ z0hQHr*ms^=+R={jj0O4TA>cO>jsor62rE*l39a$$v1BcbK-`Y-vLg3&;^_elG?b4OoJGgZ0gd)f!AbYnn)JWB%SR3WSVMskpu9975R$0AkT^`aty z2I+O`l1hRvS5cd?>F{i|gtV$f@wu!>xLJFlMb_A4=6b?dNsU^pUTU!|N#ooV&bx`1 z-1fA+^Uy;!SW8uf5cyhxB3iTDBAn8bE%FoOrFQ%udgrgg8=;`vF{k6p=|CzL@Qu@o zT>FUKMHC>8D%C|!Kh0x>7@-z$(F+i45k(>+OYTu#l}%X&8&R8reo41M;2=rhgorSD z%oX5zU`8->TLg;i|4_7umj#|F0ELy6BQT(gN<80KU_e9eswl~5Ts6;tz>mV9&sU7c zJhybpIhzkWZg0Mp8G}P`qzfk3ILY`y_-R5@pE2?TTK{B_OK>!99C1!F+cc2|%9&J^ z^$6?M51k}K$geYFG;^ak$2%BNlmNmYvj>atbxF-xT`^ICbU$>}*flw7uO~bC zcHB{_j_~Yg)i3YXDPQ###bBXYCZ4uTQ$xB6i)}qTDu2{RVG&baAzPP4<^DwD=Mv+s zTBF9v6%$|SdF1^&${&bF#}}7-cxT58+OYOWb_G!Qy3HXu zYG1a6>~byzwtKJJRzMIRr9r>DAnAgw;7$etu)0T*K};=#hZs%33%hIRA)f~y1!t$O zwvaGeK-K(k4nSjlCqL%%2Vp@3pCdUmtfhz2lw4xd$UU=DqzOeJ3rIjiWcgu;7l8}V z=egO}6R_NnV6xT#AQa7xh$(L2)U^f0^YgBg?4p^V0Nmfl@oB^dQuZzcKT}5@yY?R{*eKjnwO^_UU9VFKVDFg=W{l;?Pi$suI;A?}dMXL^oyzGQ8{ z4rMfjWWJcI?pha@f>EFqAq?9;Y`3wrpz~ps%j4srNoi5_kEf#v#gztI7HLURqf z@b)=|kkCN_?+6J-!Y9OF(IP6~-VgiAs&!DkTMG)81gpMsY|p;N6|{fRZ*ZibrP$od zXFU$Rk!ARZ%z<|rO1Gut$t)2S^H{gRnWU8Vfe;a?UhF5OaGq9D*t!$r&O;d5ca~-R zcZQqZeGuff58?AKO;=+{${yrm)lYX8lNGcK?Bj~$O1I`hhz%cAY4397qfR^Saxx{e zR;&-u^Y>W^E4glU8lBYTVNm}8VTp- zPVhQ^iue+l2vx6P>H%9Pha20q+_7)xx4=2#G%>(T&4NoA9_A4Z@Jrcj7}CaJbH*<0 zS|@m!+UvaU0DfHv6tI!}xsw<)aI&vcG!&|caow85zY+#WB~q1grL$NSGBymUqiAIn z8SVagakXJ(*r3q<#L*Ni;9S}#}P5147sTk*+2 zips%~i-#JkMWPFTP*IZpT^;_lrb6_Ni29uL%2hA>*vM;s#cd6LV&MVgzYHecm+Mnz zcO9QcTJO#om|%<=QNmYLi3Inb3LCr_(%p%$`UltMT+t=odT^04<8+nX+c14NQ<;Kq z{U#{O8B@OQUs7T#Apc(OC$`B_%1r9Z zkFg05Fa!`#x)E^v#2_Kj1%<^SF~5nrB4`J|lTb|RcKLz80}+u7$z==r!})_JSLycp z!oc$a2LvM!3xvXQK*txG3P*s`a$d6Nbp=B)qm`@ahM>Zh8!YOE;kH%}`tq=E^=YTu zb)&Mao^>J9x1H(XcU1f1KXCZ)0f8b3GZ5mC#52IzB@^S2rb9*|en6S(Ly*Kd!y)$- zP9}+wf~HaUK-~WE9RLzoD4T)(b?RbJ#m?<3PNeuY!Tzf6@OyqOng1#=e=*OLNwd7c z`T93?tp6uT0r0=>^fGmD37+S&{;-)R%loiZX4CQTtw_h~VFn0@VjlxQh6IBhAji(| zI*kjI0O9|u+y5Zy|EY7<)?TRp0b8Gc7139{Xj$dqZg2Ah@FQRV2X-ThqhR^>e}fW6 zN)dd^iJ<+fFlgk>{dgxDx1?WRU+ZG)Xzy(2)*Bg zmG4LtH?ju7s1&hb013YduhKli4=Gj%gd!Y{?Q|Hr*EhDYt(&(qA@JOC<;(!$ zCJgG9a7lwfx}g2GekhP4`XNeWsuX{<@RvTm&R3)5<)$VX8LF%7Y|nl|L52i>5dS(v zNl{X4X8L0v69bi*ky^eqT!e!fyA>KPp;W0%zD%YaTqatS&RROP-V_WVpbLotE=C}4 z3X2v*j_|<{`YrYMt84WCk51>u*Sn{_F7?m7`=4k8C{`+YuaU8?3yPp8h>5OXW{~j? zib{^5TL)oiG=VE+csOo6jlMJ29B}a2>`hA`k%yb2f2+#Bs3}x8Wau~04^yyj+X8(J zE&cJk`ddxtnl_5E7PD3_j3PJn#wYeK58HR{-T(wYM3i2r*j&uSZ7}&i#>1Z>G>&B? ztfn-r;V{T9HOZ-U1wXRrV#i(R-&u@T+XA5Z-2qUD$QW^%iDK7B{uKYQsjryZTL;E2 zH$N}`-(glnp?*l++`K9ti{__ey=;2(Qn}Z}&KnOV*|gJs*$F_Nko@0Qv8kp86lH_}Vf2;LM$UyFhl8qYmM)r~j*UO7R-qhtGUXWK)uQ) zUub>`fHL|x^oHx$d2acE=-#aJ6NAh}jrD__AcLPiyMO{quYDVVL`|-Fi=IZqw!0UC zs9mhzzq+OI!)Moyb$q~oLy2Fl7arg1rSBT8om>ZP|M=9rj_~gV83r)&%kPQ)HGmnY z$d=F9C9~6LD;`n>#D@nzs7t}UP#?SrtOl?C(;)ut=6d_TLFoSyYY1QC5fTw*nn9Rc z66(@I>_Q{1N^(X%$UOEBwQYc^@`E>IKuOTvN0n#t;Bq2u)H~YuYy`dN$cxMYW`FyA zM6!50!vCVbD^bY*Uei$w-Q-oAX8z%o_RZkM)iZ$LSA6uJ@!5mWLm@YN-9+}6hu&tP zKXh*7!;#$^x%!-qArp-T+?0tQAfKMEt_@K8-2pICSgM%H#5xLe z{;BYbpTMhtgS>_-|3AK}BQ4BfZFqprI z@Gshn5dLq;MerXj?@H7~3t#4!8wda)h5%za0cENlhc*JKy8Qb!2HF)np5M~(X|wd@ z@|Sp68b7XbRJFs+gF;>13nq_1!q1}0<;B_i2J6QJh%eKkMKbvvzt4pbEj%feo`tIJ z5M*I&fM)nkS}mMSi^e!(zRN5M5BA_k{f$!lsiW)kp+ALW-Qn3CVZ~1*0>dkJL`mI{eUIv zr0PVDneJIJzdMx@t%VXY1I66moi_WWbJuSyr#IOnDrL{}Tw=)Y((vnizL6AlPE&WA zI(39bI&A`)^A}|rbmIeH8T5)D2`n1*pRa&ThyCn;=6{gyX(UuQ(bl61%-0v2PM!qk zDzmHhT;nc?8hx3~vw*VqJli2z*z7pe(F1g_!TwUH1lIYm5zydQ^Cn&pamCx_-Kic5 zM8V7Kj`&ybz3%l>tc&1Al>b+}vaCi=2l01Y?rzr%_|pgkf}Xt?-?|kTT5Aop4fn(a z_e2;rW$>$*glXPA^(PuNEbDZ|bDj5N8{m#TTs3}YT&f0=URx&xfd12nBriLX#}hZKf)qIS|u zo$1A$u2NW8dQi|I{-MzaK-QG3^ryiEHt$;J^xr7G;pLi-@fH!aH=#tXOp5z=Ir_Xs zKhp#5`@Bc*r*w1p%`!Rj0yL6os8d3_^^}Ku)u)g&dy=zpo zjvhdWgvI29FPpqkf^1Dk$idzQ)M1CV>hNX%BEEn6C#4QwovS2*tx?w{KL00pzFWZq zm%wwA%{RhW!}s9wzd7DF$sE@L{zujhKmP$m|9}I3K(aqz;UCaA07`ke&f$&D=lyk( zzvvw;;jsq&#;nS^YVAhwH*qa<2SL#531nSE`-h2Zc>f^M@*Ai0HWVNtsp9EE&j9Iv z0ja z{nCH$=Ebzf_H91zzJ~ONvcHJ`yA<{rt z|A44}!1u4aZKARVwoqlp+KgF1CbJmB)G{31DoY_F2ra$0 zIbRsXhzRsfFEHx_PuASK{2Vdo|zW{{fHGcYK z?g4+(X`tBG6g_c(4ThX!T&ac%mKe&Obl}g0Te{DtDAcd`Zt|V&-`!|`~ z?!53FwS!GL!xc@h5rK?kgh3A9oQ=FzdP)M2L2>s|%GK5@*H(8t47t*Va-<`)>o+i+ z4HrwanCltdbjwa1fcTvE7&AxPfyN$)yoBzen7RNn z9t4X&#&6V@9(C;L<-Z@t&fhPkAGUn;Ni=_38*CfT6A;4Z`zz;niiKt}RIe|_62DF5 zpB8jF*DKGf*1L@`ZN-H5ee}fRX>aB7HTLz?PWK6*2vSwVWe|+UVxr9trn(RWF}6kU z*k2uT+Ym@UMo{=awulWXw13TYJi_t>afg+StuBD&Cc1UhipC}}oz}+fgkqt&8K|w< zp}JY9rJ1>^8U1nFI_0F{GkOx-KZ?}Mg_CPEZkY=K%sG(7IZ64@{}LhTRL=JVRPJ)k zORRH+Gbi~4BiT#yfSA|efRg`LBBH(QM*4?Shv`1ix(%dxCpysU?BYz`t4${edhmq8 zi~hunh1SH(!qslc7KX2Rd+LIpRrEC*85v7YjQ?H&S0a4A5FVIQKkosAr5_Vr^}?Vt zUtxv7??)?L>->Zw!zGp+1F-K68-3h9#q+2%0Z05#AonYpo7dC^*7sMxuQ*Wfsv&!o zV1G;Zp_9-bFsjO?qW=1dAsZW!?DCp-SeRm@2mobt$0VZ-_hvYMhaGQ$iQ%u1q=oSx ztJVU=Kag4 zelQ1sAiE#A|8W!!3tCiGEYpqnhwfkm;D5);|27Dk`euRpD|~wQkA_&k1~k9(Z|!gX z>2DAoPCIK_yAkYF29Ml&H4C3FOfdaS(f2dTl7ME7S+iws`B$~rGzqiBegf$Yn7@-% z2pkHeAcP%D-1p!e{=SEiuCcW!oK)8Ggb}3C=B~Luz=>v9B;x>!9@#>h3G z^rzI-leCi#sAAk@n8*t|)>F{k@d(~@h#Zt?0yU6$4k>CZ+@GU#ftX)t>4pkyryO1y zogd2*!w2B2*6y*F`67H)#@a5~HHPE1qP6n{Ym_l!Z?0w8&5W&mY9VNo_fuwyx%1J`D)q}$ip&e{ zYUe1=Vdb{-=KSSx3az!J-BXkWqG~y%h5c!p4Bci2=RiGA-xd;7bJI>w;)Uk;8Ieiw zP;zvb#0eo0u_Gp4DJ%QzNVBi`UcP|3N!q9KjUg>g{j73k(GwCvp0yO2Ltwe7e95Fj zKbD|Apm<6bI zd}5PY3Bdw3Wsam>OcW|{my)=iuRrR+x#)SpYHVw^8{7WJ;NfT?!J2{!1w@q}8Yw9j zJBg$F2q5$@X1s|V{j|c<;rXnEsf}t`nqxo|qn}o%4HJ?tzm>Sh)X|LIct$Wu5i90= z1tGKkF55=%OiTMD7$p{TwqFoWUN4T#!eAG08z;XizI;70LnLx$JB6 HYJ&e4C-=I8 diff --git a/misc/DejaVuSerif.woff2 b/misc/DejaVuSerif.woff2 deleted file mode 100644 index 238566d677f916c83ca2f8425e33e96c8be7a285..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146864 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

@X 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 diff --git a/misc/javadoc-overwrites.css b/misc/javadoc-overwrites.css new file mode 100644 index 0000000..7eed81f --- /dev/null +++ b/misc/javadoc-overwrites.css @@ -0,0 +1,2 @@ +:root { --body-font-size: 16px;} +:root { --code-font-size: 16px;} diff --git a/misc/stylesheet.css b/misc/stylesheet.css deleted file mode 100644 index 625b5ae..0000000 --- a/misc/stylesheet.css +++ /dev/null @@ -1,904 +0,0 @@ -/* - * Javadoc style sheet - */ - -@font-face { - font-family: 'DejaVu Serif'; - src: local('DejaVu Serif'), url('DejaVuSerif.woff2'); -} - -@font-face { - font-family: 'DejaVu Serif'; - font-weight: bold; - src: local('DejaVu Serif Bold'), url('DejaVuSerif-Bold.woff2'); -} - -@font-face { - font-family: 'DejaVu Sans'; - src: local('DejaVu Sans'), url('DejaVuSans.woff2'); -} - -@font-face { - font-family: 'DejaVu Sans'; - font-weight: bold; - src: local('DejaVu Sans Bold'), url('DejaVuSans-Bold.woff2'); -} - -@font-face { - font-family: 'DejaVu Sans Mono'; - src: local('DejaVu Sans Mono'), url('DejaVuSansMono.woff2'); -} - -@font-face { - font-family: 'DejaVu Sans Mono'; - font-weight: bold; - src: local('DejaVu Sans Mono Bold'), url('DejaVuSansMono-Bold.woff2'); -} - -/* - * Styles for individual HTML elements. - * - * These are styles that are specific to individual HTML elements. Changing them affects the style of a particular - * HTML element throughout the page. - */ - -body { - background-color:#ffffff; - color:#353833; - font: normal 16px/1.5 "DejaVu Sans", Arial, Helvetica, sans-serif; - margin:0; - padding:0; - height:100%; - width:100%; -} -iframe { - margin:0; - padding:0; - height:100%; - width:100%; - overflow-y:scroll; - border:none; -} -a:link, a:visited { - text-decoration:none; - color:#4A6782; -} -a[href]:hover, a[href]:focus { - text-decoration:none; - color:#bb7a2a; -} -a[name] { - color:#353833; -} -pre { - font-family:'DejaVu Sans Mono', monospace; -} -h1 { - font-size:20px; -} -h2 { - font-size:18px; -} -h3 { - font-size:17px; -} -h4 { - font-size:16px; - margin-top: 1rem; - margin-bottom: 1rem; -} -h5 { - font-size:14px; -} -h6 { - font-size:13px; -} -ul { - list-style-type:disc; -} -code, tt { - font-family:'DejaVu Sans Mono', monospace; -} -:not(h1, h2, h3, h4, h5, h6) > code, -:not(h1, h2, h3, h4, h5, h6) > tt { - /* font-size:14px; */ - padding-top:4px; - margin-top:8px; - line-height:1.4em; -} -dt code { - font-family:'DejaVu Sans Mono', monospace; - font-size:14px; - padding-top:4px; -} -.summary-table dt code { - font-family:'DejaVu Sans Mono', monospace; - font-size:14px; - vertical-align:top; - padding-top:4px; -} -sup { - font-size:8px; -} -button { - font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif; -} -/* - * Styles for HTML generated by javadoc. - * - * These are style classes that are used by the standard doclet to generate HTML documentation. - */ - -/* - * Styles for document title and copyright. - */ -.clear { - clear:both; - height:0; - overflow:hidden; -} -.about-language { - float:right; - padding:0 21px 8px 8px; - font-size:11px; - margin-top:-9px; - height:2.9em; -} -.legal-copy { - margin-left:.5em; -} -.tab { - background-color:#0066FF; - color:#ffffff; - padding:8px; - width:5em; - font-weight:bold; -} -/* - * Styles for navigation bar. - */ -@media screen { - .flex-box { - position:fixed; - display:flex; - flex-direction:column; - height: 100%; - width: 100%; - } - .flex-header { - flex: 0 0 auto; - } - .flex-content { - flex: 1 1 auto; - overflow-y: auto; - } -} -.top-nav { - background-color:#4D7A97; - color:#FFFFFF; - float:left; - padding:0; - width:100%; - clear:right; - min-height:2.8em; - padding-top:10px; - overflow:hidden; - font-size:80%; -} -.sub-nav { - background-color:#dee3e9; - float:left; - width:100%; - overflow:hidden; - font-size:80%; -} -.sub-nav div { - clear:left; - float:left; - padding:0 0 5px 6px; - text-transform:uppercase; -} -.sub-nav .nav-list { - padding-top:5px; -} -ul.nav-list { - display:block; - margin:0 25px 0 0; - padding:0; -} -ul.sub-nav-list { - float:left; - margin:0 25px 0 0; - padding:0; -} -ul.nav-list li { - list-style:none; - float:left; - padding: 5px 6px; - text-transform:uppercase; -} -.sub-nav .nav-list-search { - float:right; - margin:0 0 0 0; - padding:5px 6px; - clear:none; -} -.nav-list-search label { - position:relative; - right:-16px; -} -ul.sub-nav-list li { - list-style:none; - float:left; - padding-top:10px; -} -.top-nav a:link, .top-nav a:active, .top-nav a:visited { - color:#FFFFFF; - text-decoration:none; - text-transform:uppercase; -} -.top-nav a:hover { - text-decoration:none; - color:#bb7a2a; - text-transform:uppercase; -} -.nav-bar-cell1-rev { - background-color:#F8981D; - color:#253441; - margin: auto 5px; -} -.skip-nav { - position:absolute; - top:auto; - left:-9999px; - overflow:hidden; -} -/* - * Hide navigation links and search box in print layout - */ -@media print { - ul.nav-list, div.sub-nav { - display:none; - } -} -/* - * Styles for page header and footer. - */ -.title { - color:#2c4557; - margin:10px 0; -} -.sub-title { - margin:5px 0 0 0; -} -.header ul { - margin:0 0 15px 0; - padding:0; -} -.header ul li, .footer ul li { - list-style:none; - font-size:80%; -} -/* - * Styles for headings. - */ -body.class-declaration-page .summary h2, -body.class-declaration-page .details h2, -body.class-use-page h2, -body.module-declaration-page .block-list h2 { - font-style: italic; - padding:0; - margin:15px 0; -} -body.class-declaration-page .summary h3, -body.class-declaration-page .details h3, -body.class-declaration-page .summary .inherited-list h2 { - background-color:#dee3e9; - border:1px solid #d0d9e0; - margin:0 0 6px -8px; - padding:7px 5px; -} -/* - * Styles for page layout containers. - */ -main { - clear:both; - padding:10px 20px; - position:relative; -} -dl.notes > dt { - font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif; - /* font-size:12px; */ - font-weight:bold; - margin:10px 0 0 0; - color:#4E4E4E; -} -dl.notes > dd { - margin:5px 10px 0 0; - /* font-size:14px; */ - font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; -} -dl.name-value > dt { - margin-left:1px; - /* font-size:1.1em; */ - display:inline; - font-weight:bold; -} -dl.name-value > dd { - margin:0 0 0 1px; - /* font-size:1.1em; */ - display:inline; -} -/* - * Styles for lists. - */ -li.circle { - list-style:circle; -} -ul.horizontal li { - display:inline; - /* font-size:0.9em; */ -} -div.inheritance { - margin:0; - padding:0; -} -div.inheritance div.inheritance { - margin-left:2em; -} -ul.block-list, -ul.details-list, -ul.member-list, -ul.summary-list { - margin:10px 0 10px 0; - padding:0; -} -ul.block-list > li, -ul.details-list > li, -ul.member-list > li, -ul.summary-list > li { - list-style:none; - margin-bottom:15px; - line-height:1.4; -} -.summary-table dl, .summary-table dl dt, .summary-table dl dd { - margin-top:0; - margin-bottom:1px; -} -ul.see-list, ul.see-list-long { - padding-left: 0; - list-style: none; -} -ul.see-list li { - display: inline; -} -ul.see-list li:not(:last-child):after, -ul.see-list-long li:not(:last-child):after { - content: ", "; - white-space: pre-wrap; -} -/* - * Styles for tables. - */ -.summary-table, .details-table { - width:100%; - border-spacing:0; - border-left:1px solid #EEE; - border-right:1px solid #EEE; - border-bottom:1px solid #EEE; - padding:0; -} - -.summary-table .col-first { - font-family: "DejaVu Sans Mono", monospace; -} - -.caption { - position:relative; - text-align:left; - background-repeat:no-repeat; - color:#253441; - font-weight:bold; - clear:none; - overflow:hidden; - padding:0; - padding-top:10px; - padding-left:1px; - margin:0; - white-space:pre; -} -.caption a:link, .caption a:visited { - color:#1f389c; -} -.caption a:hover, -.caption a:active { - color:#FFFFFF; -} -.caption span { - white-space:nowrap; - padding-top:5px; - padding-left:12px; - padding-right:12px; - padding-bottom:7px; - display:inline-block; - float:left; - background-color:#F8981D; - border: none; - height:16px; -} -div.table-tabs { - padding:10px 0 0 1px; - margin:0; -} -div.table-tabs > button { - border: none; - cursor: pointer; - padding: 5px 12px 7px 12px; - font-weight: bold; - margin-right: 3px; -} -div.table-tabs > button.active-table-tab { - background: #F8981D; - color: #253441; -} -div.table-tabs > button.table-tab { - background: #4D7A97; - color: #FFFFFF; -} -.two-column-summary { - display: grid; - grid-template-columns: minmax(15%, max-content) minmax(15%, auto); -} -.three-column-summary { - display: grid; - grid-template-columns: minmax(10%, max-content) minmax(15%, max-content) minmax(15%, auto); -} -.four-column-summary { - display: grid; - grid-template-columns: minmax(10%, max-content) minmax(10%, max-content) minmax(10%, max-content) minmax(10%, auto); -} -@media screen and (max-width: 600px) { - .two-column-summary { - display: grid; - grid-template-columns: 1fr; - } -} -@media screen and (max-width: 800px) { - .three-column-summary { - display: grid; - grid-template-columns: minmax(10%, max-content) minmax(25%, auto); - } - .three-column-summary .col-last { - grid-column-end: span 2; - } -} -@media screen and (max-width: 1000px) { - .four-column-summary { - display: grid; - grid-template-columns: minmax(15%, max-content) minmax(15%, auto); - } -} -.summary-table > div, .details-table > div { - text-align:left; - padding: 8px 3px 3px 7px; -} -.col-first, .col-second, .col-last, .col-constructor-name, .col-summary-item-name { - vertical-align:top; - padding-right:0; - padding-top:8px; - padding-bottom:3px; -} -.table-header { - background:#dee3e9; - font-weight: bold; -} -/* -.col-first, .col-first { - font-size:13px; -} -.col-second, .col-second, .col-last, .col-constructor-name, .col-summary-item-name, .col-last { - font-size:13px; -} -*/ -.col-first, .col-second, .col-constructor-name { - vertical-align:top; - overflow: auto; -} -.col-last { - white-space:normal; -} -.col-first a:link, .col-first a:visited, -.col-second a:link, .col-second a:visited, -.col-first a:link, .col-first a:visited, -.col-second a:link, .col-second a:visited, -.col-constructor-name a:link, .col-constructor-name a:visited, -.col-summary-item-name a:link, .col-summary-item-name a:visited, -.constant-values-container a:link, .constant-values-container a:visited, -.all-classes-container a:link, .all-classes-container a:visited, -.all-packages-container a:link, .all-packages-container a:visited { - font-weight:bold; -} -.table-sub-heading-color { - background-color:#EEEEFF; -} -.even-row-color, .even-row-color .table-header { - background-color:#FFFFFF; -} -.odd-row-color, .odd-row-color .table-header { - background-color:#EEEEEF; -} -/* - * Styles for contents. - */ -.deprecated-content { - margin:0; - padding:10px 0; -} -div.block { - font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; -} -.col-last div { - padding-top:0; -} -.col-last a { - padding-bottom:3px; -} -.module-signature, -.package-signature, -.type-signature, -.member-signature { - font-family:'DejaVu Sans Mono', monospace; - margin:14px 0; - white-space: pre-wrap; -} -.module-signature, -.package-signature, -.type-signature { - margin-top: 0; -} -.member-signature .type-parameters-long, -.member-signature .parameters, -.member-signature .exceptions { - display: inline-block; - vertical-align: top; - white-space: pre; -} -.member-signature .type-parameters { - white-space: normal; -} -/* - * Styles for formatting effect. - */ -.source-line-no { - color:green; - padding:0 30px 0 0; -} -h1.hidden { - visibility:hidden; - overflow:hidden; - /* font-size:10px; */ -} -.block { - display:block; - margin:0 10px 5px 0; - color:#474747; -} -.deprecated-label, .descfrm-type-label, .implementation-label, .member-name-label, .member-name-link, -.module-label-in-package, .module-label-in-type, .override-specify-label, .package-label-in-type, -.package-hierarchy-label, .type-name-label, .type-name-link, .search-tag-link, .preview-label { - font-weight:bold; -} -.deprecation-comment, .help-footnote, .preview-comment { - font-style:italic; -} -.deprecation-block { - /* font-size:14px; */ - font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; - border-style:solid; - border-width:thin; - border-radius:10px; - padding:10px; - margin-bottom:10px; - margin-right:10px; - display:inline-block; -} -.preview-block { - /* font-size:14px; */ - font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; - border-style:solid; - border-width:thin; - border-radius:10px; - padding:10px; - margin-bottom:10px; - margin-right:10px; - display:inline-block; -} -div.block div.deprecation-comment { - font-style:normal; -} -/* - * Styles specific to HTML5 elements. - */ -main, nav, header, footer, section { - display:block; -} -/* - * Styles for javadoc search. - */ -.ui-autocomplete-category { - font-weight:bold; - /* font-size:15px; */ - padding:7px 0 7px 3px; - background-color:#4D7A97; - color:#FFFFFF; -} -.result-item { - /* font-size:13px; */ -} -.ui-autocomplete { - max-height:85%; - max-width:65%; - overflow-y:scroll; - overflow-x:scroll; - white-space:nowrap; - box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); -} -ul.ui-autocomplete { - position:fixed; - z-index:999999; - background-color: #FFFFFF; -} -ul.ui-autocomplete li { - float:left; - clear:both; - width:100%; -} -.result-highlight { - font-weight:bold; -} -.ui-autocomplete .result-item { - font-size: inherit; -} -#search-input { - background-image:url('resources/glass.png'); - background-size:13px; - background-repeat:no-repeat; - background-position:2px 3px; - padding-left:20px; - position:relative; - right:-18px; - width:400px; -} -#reset-button { - background-color: rgb(255,255,255); - background-image:url('resources/x.png'); - background-position:center; - background-repeat:no-repeat; - background-size:12px; - border:0 none; - width:16px; - height:16px; - position:relative; - left:-4px; - top:-4px; - font-size:0px; -} -.watermark { - color:#545454; -} -.search-tag-desc-result { - font-style:italic; - /* font-size:11px; */ -} -.search-tag-holder-result { - font-style:italic; - /* font-size:12px; */ -} -.search-tag-result:target { - background-color:yellow; -} -.module-graph span { - display:none; - position:absolute; -} -.module-graph:hover span { - display:block; - margin: -100px 0 0 100px; - z-index: 1; -} -.inherited-list { - margin: 10px 0 10px 0; -} -section.class-description { - line-height: 1.4; -} -.summary section[class$="-summary"], .details section[class$="-details"], -.class-uses .detail, .serialized-class-details { - padding: 0px 20px 5px 10px; - border: 1px solid #ededed; - background-color: #f8f8f8; -} -.inherited-list, section[class$="-details"] .detail { - padding:0 0 5px 8px; - background-color:#ffffff; - border:none; -} -.vertical-separator { - padding: 0 5px; -} -ul.help-section-list { - margin: 0; -} -ul.help-subtoc > li { - display: inline-block; - padding-right: 5px; - /* font-size: smaller; */ -} -ul.help-subtoc > li::before { - content: "\2022" ; - padding-right:2px; -} -span.help-note { - font-style: italic; -} -/* - * Indicator icon for external links. - */ -main a[href*="://"]::after { - content:""; - display:inline-block; - background-image:url('data:image/svg+xml; utf8, \ - \ - \ - '); - background-size:100% 100%; - width:7px; - height:7px; - margin-left:2px; - margin-bottom:4px; -} -main a[href*="://"]:hover::after, -main a[href*="://"]:focus::after { - background-image:url('data:image/svg+xml; utf8, \ - \ - \ - '); -} - -/* - * Styles for user-provided tables. - * - * borderless: - * No borders, vertical margins, styled caption. - * This style is provided for use with existing doc comments. - * In general, borderless tables should not be used for layout purposes. - * - * plain: - * Plain borders around table and cells, vertical margins, styled caption. - * Best for small tables or for complex tables for tables with cells that span - * rows and columns, when the "striped" style does not work well. - * - * striped: - * Borders around the table and vertical borders between cells, striped rows, - * vertical margins, styled caption. - * Best for tables that have a header row, and a body containing a series of simple rows. - */ - -table.borderless, -table.plain, -table.striped { - margin-top: 10px; - margin-bottom: 10px; -} -table.borderless > caption, -table.plain > caption, -table.striped > caption { - font-weight: bold; - /* font-size: smaller; */ -} -table.borderless th, table.borderless td, -table.plain th, table.plain td, -table.striped th, table.striped td { - padding: 2px 5px; -} -table.borderless, -table.borderless > thead > tr > th, table.borderless > tbody > tr > th, table.borderless > tr > th, -table.borderless > thead > tr > td, table.borderless > tbody > tr > td, table.borderless > tr > td { - border: none; -} -table.borderless > thead > tr, table.borderless > tbody > tr, table.borderless > tr { - background-color: transparent; -} -table.plain { - border-collapse: collapse; - border: 1px solid black; -} -table.plain > thead > tr, table.plain > tbody tr, table.plain > tr { - background-color: transparent; -} -table.plain > thead > tr > th, table.plain > tbody > tr > th, table.plain > tr > th, -table.plain > thead > tr > td, table.plain > tbody > tr > td, table.plain > tr > td { - border: 1px solid black; -} -table.striped { - border-collapse: collapse; - border: 1px solid black; -} -table.striped > thead { - background-color: #E3E3E3; -} -table.striped > thead > tr > th, table.striped > thead > tr > td { - border: 1px solid black; -} -table.striped > tbody > tr:nth-child(even) { - background-color: #EEE -} -table.striped > tbody > tr:nth-child(odd) { - background-color: #FFF -} -table.striped > tbody > tr > th, table.striped > tbody > tr > td { - border-left: 1px solid black; - border-right: 1px solid black; -} -table.striped > tbody > tr > th { - font-weight: normal; -} -/** - * Tweak font sizes and paddings for small screens. - */ -@media screen and (max-width: 1050px) { - #search-input { - width: 300px; - } -} -@media screen and (max-width: 800px) { - #search-input { - width: 200px; - } - .top-nav, - .bottom-nav { - font-size: 80%; - padding-top: 6px; - } - .sub-nav { - font-size: 80%; - } - .about-language { - padding-right: 16px; - } - ul.nav-list li, - .sub-nav .nav-list-search { - padding: 6px; - } - ul.sub-nav-list li { - padding-top: 5px; - } - main { - padding: 10px; - } - .summary section[class$="-summary"], .details section[class$="-details"], - .class-uses .detail, .serialized-class-details { - padding: 0 8px 5px 8px; - } - body { - -webkit-text-size-adjust: none; - } -} -@media screen and (max-width: 500px) { - #search-input { - width: 150px; - } - .top-nav, - .bottom-nav { - font-size: 80%; - } - .sub-nav { - font-size: 80%; - } - .about-language { - font-size: 80%; - padding-right: 12px; - } -} diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Containerfile b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Containerfile index c212945..08c4bff 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Containerfile +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Containerfile @@ -1,4 +1,4 @@ -FROM docker.io/eclipse-temurin:17-jre-alpine +FROM docker.io/eclipse-temurin:21-jre-alpine COPY build/install/vm-manager /opt/vmmanager diff --git a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java index 437790b..1984f89 100644 --- a/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java +++ b/org.jdrupes.vmoperator.manager/src/org/jdrupes/vmoperator/manager/Reconciler.java @@ -184,12 +184,8 @@ public class Reconciler extends Component { * @param event the event * @param channel the channel * @throws ApiException the api exception - * @throws IOException - * @throws ParseException - * @throws MalformedTemplateNameException - * @throws TemplateNotFoundException - * @throws TemplateException - * @throws KubectlException + * @throws TemplateException the template exception + * @throws IOException Signals that an I/O exception has occurred. */ @Handler @SuppressWarnings("PMD.ConfusingTernary") diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CdMediaController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CdMediaController.java index 53922f2..0a8971c 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CdMediaController.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CdMediaController.java @@ -27,7 +27,7 @@ import org.jdrupes.vmoperator.runner.qemu.commands.QmpOpenTray; import org.jdrupes.vmoperator.runner.qemu.commands.QmpRemoveMedium; import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; -import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; +import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState; import org.jdrupes.vmoperator.runner.qemu.events.TrayMovedEvent; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; @@ -69,7 +69,7 @@ public class CdMediaController extends Component { @SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition", "PMD.AvoidInstantiatingObjectsInLoops" }) public void onConfigureQemu(ConfigureQemu event) { - if (event.state() == State.TERMINATING) { + if (event.runState() == RunState.TERMINATING) { return; } @@ -82,7 +82,7 @@ public class CdMediaController extends Component { } var driveId = "cd" + cdCounter++; var newFile = Optional.ofNullable(drives[i].file).orElse(""); - if (event.state() == State.STARTING) { + if (event.runState() == RunState.STARTING) { current.put(driveId, newFile); continue; } @@ -116,8 +116,8 @@ public class CdMediaController extends Component { */ @Handler public void onTrayMovedEvent(TrayMovedEvent event) { - trayState.put(event.driveId(), event.state()); - if (event.state() == TrayState.OPEN + trayState.put(event.driveId(), event.trayState()); + if (event.trayState() == TrayState.OPEN && pending.containsKey(event.driveId())) { changeMedium(event.driveId()); } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine index b87049e..d0104f3 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine @@ -2,7 +2,7 @@ FROM docker.io/alpine RUN apk update -RUN apk add qemu-system-x86_64 qemu-modules ovmf swtpm openjdk17 mtools +RUN apk add qemu-system-x86_64 qemu-modules ovmf swtpm openjdk21 mtools RUN mkdir -p /etc/qemu && echo "allow all" > /etc/qemu/bridge.conf diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch index 2ccb2f9..6ba8500 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch @@ -5,7 +5,7 @@ RUN systemd-firstboot RUN pacman-key --init \ && pacman -Sy --noconfirm archlinux-keyring && pacman -Su --noconfirm \ && pacman -S --noconfirm which qemu-full virtiofsd \ - edk2-ovmf swtpm iproute2 bridge-utils jre17-openjdk-headless \ + edk2-ovmf swtpm iproute2 bridge-utils jre21-openjdk-headless \ mtools \ && pacman -Scc --noconfirm diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java index 857b14e..b0abfd4 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/CpuController.java @@ -33,7 +33,7 @@ import org.jdrupes.vmoperator.runner.qemu.events.CpuAdded; import org.jdrupes.vmoperator.runner.qemu.events.CpuDeleted; import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; -import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; +import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.annotation.Handler; @@ -64,7 +64,7 @@ public class CpuController extends Component { */ @Handler public void onConfigureQemu(ConfigureQemu event) { - if (event.state() == State.TERMINATING) { + if (event.runState() == RunState.TERMINATING) { return; } Optional.ofNullable(event.configuration().vm.currentCpus) diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java index dc73cb2..1f9833c 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/DisplayController.java @@ -27,7 +27,7 @@ import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetDisplayPassword; import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetPasswordExpiry; import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; -import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; +import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; import org.jgrapes.core.annotation.Handler; @@ -67,7 +67,7 @@ public class DisplayController extends Component { */ @Handler public void onConfigureQemu(ConfigureQemu event) { - if (event.state() == State.TERMINATING) { + if (event.runState() == RunState.TERMINATING) { return; } protocol diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java index 4d9f479..c837537 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Runner.java @@ -61,7 +61,7 @@ import org.jdrupes.vmoperator.runner.qemu.events.Exit; import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand; import org.jdrupes.vmoperator.runner.qemu.events.QmpConfigured; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; -import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; +import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState; import org.jdrupes.vmoperator.util.ExtendedObjectWrapper; import org.jdrupes.vmoperator.util.FsdUtils; import org.jgrapes.core.Channel; @@ -217,7 +217,7 @@ public class Runner extends Component { private CommandDefinition qemuDefinition; private final QemuMonitor qemuMonitor; private Integer resetCounter; - private State state = State.INITIALIZING; + private RunState state = RunState.INITIALIZING; /** Preparatory actions for QEMU start */ @SuppressWarnings("PMD.FieldNamingConventions") @@ -467,7 +467,7 @@ public class Runner extends Component { */ @Handler public void onStarted(Started event) { - state = State.STARTING; + state = RunState.STARTING; rep.fire(new RunnerStateChange(state, "RunnerStarted", "Runner has been started")); // Start first process(es) @@ -618,9 +618,9 @@ public class Runner extends Component { */ @Handler(priority = -1000) public void onConfigureQemuFinal(ConfigureQemu event) { - if (state == State.STARTING) { + if (state == RunState.STARTING) { fire(new MonitorCommand(new QmpCont())); - state = State.RUNNING; + state = RunState.RUNNING; rep.fire(new RunnerStateChange(state, "VmStarted", "Qemu has been configured and is continuing")); } @@ -633,7 +633,7 @@ public class Runner extends Component { */ @Handler public void onConfigureQemu(ConfigureQemu event) { - if (state == State.RUNNING) { + if (state == RunState.RUNNING) { if (resetCounter != null && event.configuration().resetCounter != null && event.configuration().resetCounter > resetCounter) { @@ -659,14 +659,14 @@ public class Runner extends Component { return; } // No other process(es) may exit during startup - if (state == State.STARTING) { + if (state == RunState.STARTING) { logger.severe(() -> "Process " + procDef.name + " has exited with value " + event.exitValue() + " during startup."); rep.fire(new Stop()); return; } - if (procDef.equals(qemuDefinition) && state == State.RUNNING) { + if (procDef.equals(qemuDefinition) && state == RunState.RUNNING) { rep.fire(new Exit(event.exitValue())); } logger.info(() -> "Process " + procDef.name @@ -693,7 +693,7 @@ public class Runner extends Component { */ @Handler(priority = 10_000) public void onStopFirst(Stop event) { - state = State.TERMINATING; + state = RunState.TERMINATING; rep.fire(new RunnerStateChange(state, "VmTerminating", "The VM is being shut down", exitStatus != 0)); } @@ -705,14 +705,14 @@ public class Runner extends Component { */ @Handler(priority = -10_000) public void onStopLast(Stop event) { - state = State.STOPPED; + state = RunState.STOPPED; rep.fire(new RunnerStateChange(state, "VmStopped", "The VM has been shut down")); } @SuppressWarnings("PMD.ConfusingArgumentToVarargsMethod") private void shutdown() { - if (!Set.of(State.TERMINATING, State.STOPPED).contains(state)) { + if (!Set.of(RunState.TERMINATING, RunState.STOPPED).contains(state)) { fire(new Stop()); } try { diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java index bbcba5e..412681f 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/StatusUpdater.java @@ -48,7 +48,7 @@ import org.jdrupes.vmoperator.runner.qemu.events.DisplayPasswordChanged; import org.jdrupes.vmoperator.runner.qemu.events.Exit; import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus; import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange; -import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; +import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState; import org.jdrupes.vmoperator.runner.qemu.events.ShutdownEvent; import org.jdrupes.vmoperator.util.GsonPtr; import org.jgrapes.core.Channel; @@ -65,8 +65,8 @@ import org.jgrapes.util.events.InitialConfiguration; @SuppressWarnings("PMD.DataflowAnomalyAnalysis") public class StatusUpdater extends Component { - private static final Set RUNNING_STATES - = Set.of(State.RUNNING, State.TERMINATING); + private static final Set RUNNING_STATES + = Set.of(RunState.RUNNING, RunState.TERMINATING); private String namespace; private String vmName; @@ -240,11 +240,11 @@ public class StatusUpdater extends Component { updateRunningCondition(event, from, cond); } }); - if (event.state() == State.STARTING) { + if (event.runState() == RunState.STARTING) { status.addProperty("ram", GsonPtr.to(from.data()) .getAsString("spec", "vm", "maximumRam").orElse("0")); status.addProperty("cpus", 1); - } else if (event.state() == State.STOPPED) { + } else if (event.runState() == RunState.STOPPED) { status.addProperty("ram", "0"); status.addProperty("cpus", 0); } @@ -252,7 +252,7 @@ public class StatusUpdater extends Component { }); // Maybe stop VM - if (event.state() == State.TERMINATING && !event.failed() + if (event.runState() == RunState.TERMINATING && !event.failed() && guestShutdownStops && shutdownByGuest) { logger.info(() -> "Stopping VM because of shutdown by guest."); var res = vmStub.patch(V1Patch.PATCH_FORMAT_JSON_PATCH, @@ -277,13 +277,13 @@ public class StatusUpdater extends Component { K8sDynamicModel from, JsonObject cond) { boolean reportedRunning = "True".equals(cond.get("status").getAsString()); - if (RUNNING_STATES.contains(event.state()) + if (RUNNING_STATES.contains(event.runState()) && !reportedRunning) { cond.addProperty("status", "True"); cond.addProperty("lastTransitionTime", Instant.now().toString()); } - if (!RUNNING_STATES.contains(event.state()) + if (!RUNNING_STATES.contains(event.runState()) && reportedRunning) { cond.addProperty("status", "False"); cond.addProperty("lastTransitionTime", diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ConfigureQemu.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ConfigureQemu.java index a8b1e45..7afa738 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ConfigureQemu.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/ConfigureQemu.java @@ -19,7 +19,7 @@ package org.jdrupes.vmoperator.runner.qemu.events; import org.jdrupes.vmoperator.runner.qemu.Configuration; -import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State; +import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState; import org.jgrapes.core.Channel; import org.jgrapes.core.Event; @@ -34,14 +34,14 @@ import org.jgrapes.core.Event; public class ConfigureQemu extends Event { private final Configuration configuration; - private final State state; + private final RunState state; /** * Instantiates a new configuration event. * * @param channels the channels */ - public ConfigureQemu(Configuration configuration, State state, + public ConfigureQemu(Configuration configuration, RunState state, Channel... channels) { super(channels); this.state = state; @@ -62,7 +62,7 @@ public class ConfigureQemu extends Event { * * @return the state */ - public State state() { + public RunState runState() { return state; } } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java index 4a7ef08..bb6ab10 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/RunnerStateChange.java @@ -31,11 +31,11 @@ public class RunnerStateChange extends Event { /** * The state. */ - public enum State { + public enum RunState { INITIALIZING, STARTING, RUNNING, TERMINATING, STOPPED } - private final State state; + private final RunState state; private final String reason; private final String message; private final boolean failed; @@ -48,7 +48,7 @@ public class RunnerStateChange extends Event { * @param message the message * @param channels the channels */ - public RunnerStateChange(State state, String reason, String message, + public RunnerStateChange(RunState state, String reason, String message, Channel... channels) { this(state, reason, message, false, channels); } @@ -62,7 +62,7 @@ public class RunnerStateChange extends Event { * @param failed the failed * @param channels the channels */ - public RunnerStateChange(State state, String reason, String message, + public RunnerStateChange(RunState state, String reason, String message, boolean failed, Channel... channels) { super(channels); this.state = state; @@ -76,7 +76,7 @@ public class RunnerStateChange extends Event { * * @return the state */ - public State state() { + public RunState runState() { return state; } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/TrayMovedEvent.java b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/TrayMovedEvent.java index f5ef725..e2d2286 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/TrayMovedEvent.java +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/events/TrayMovedEvent.java @@ -50,7 +50,7 @@ public class TrayMovedEvent extends MonitorEvent { * * @return the tray state */ - public TrayState state() { + public TrayState trayState() { return data().get("tray-open").asBoolean() ? TrayState.OPEN : TrayState.CLOSED; From 7f80f4c6e94c8fe1da1699a082a2e82879885882 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Tue, 18 Jun 2024 13:51:59 +0200 Subject: [PATCH 147/462] Wip/gitlab ci (#32) Start CI/CD on a gitlab instance. --- .gitlab-ci.yml | 64 +- gradle.properties | 1 + org.jdrupes.vmoperator.manager/build.gradle | 60 +- .../build.gradle | 101 +- .../vmoperator/runner/qemu/Containerfile.arch | 2 +- package-lock.json | 3158 ++++++++--------- package.json | 4 + spice-squid/Containerfile | 2 +- 8 files changed, 1676 insertions(+), 1716 deletions(-) create mode 100644 gradle.properties diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 70654cc..f1e59d7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,25 +1,69 @@ -default: - # Template project: https://gitlab.com/pages/jekyll - # Docs: https://docs.gitlab.com/ee/pages/ - image: ruby:3.2 +stages: + - build + - test + - publish + - deploy + +.any-job: + rules: + - if: $CI_SERVER_HOST == "gitlab.mnl.de" + +.gradle-job: + extends: .any-job + image: registry.mnl.de/org/jgrapes/jdk21-builder:v2 + cache: + - key: dependencies + policy: pull-push + paths: + - .gradle + - node_modules + - key: "$CI_COMMIT_SHA" + policy: pull-push + paths: + - build + - "*/build" + before_script: + - echo -n $CI_REGISTRY_PASSWORD | podman login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY + - git switch $CI_COMMIT_REF_NAME + - git pull + - git reset --hard $CI_COMMIT_SHA + +build-jars: + stage: build + extends: .gradle-job + script: + - ./gradlew -Pdocker.registry=$CI_REGISTRY_IMAGE build apidocs + +publish-images: + stage: publish + extends: .gradle-job + script: + - ./gradlew -Pdocker.registry=$CI_REGISTRY_IMAGE pushImage + +.pages-job: + extends: .any-job + image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/ruby:3.2 + variables: + JEKYLL_ENV: production + LC_ALL: C.UTF-8 before_script: - git fetch origin gh-pages - git checkout gh-pages - gem install bundler - bundle install -variables: - JEKYLL_ENV: production - LC_ALL: C.UTF-8 -test: + +test-pages: stage: test + extends: .pages-job script: - bundle exec jekyll build -d test artifacts: paths: - test -pages: - stage: deploy +publish-pages: + stage: publish + extends: .pages-job script: - bundle exec jekyll build -d public artifacts: diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..f97ebb7 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.parallel=true diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index c90ad54..a956a00 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -45,45 +45,36 @@ application { mainClass = 'org.jdrupes.vmoperator.manager.Manager' } +project.ext.gitBranch = grgit.branch.current.name.replace('/', '-') + task buildImage(type: Exec) { dependsOn installDist inputs.files 'src/org/jdrupes/vmoperator/manager/Containerfile' commandLine 'podman', 'build', '--pull', - '-t', "${project.name}:${project.version}",\ + '-t', "${project.name}:${project.gitBranch}",\ '-f', 'src/org/jdrupes/vmoperator/manager/Containerfile', '.' } -task tagLatestImage(type: Exec) { - dependsOn buildImage - - enabled = !project.version.contains("SNAPSHOT") - && !project.version.contains("alpha") \ - && !project.version.contains("beta") \ - || project.rootProject.properties['docker.testRegistry'] \ - && project.rootProject.properties['docker.registry'] \ - == project.rootProject.properties['docker.testRegistry'] - - commandLine 'podman', 'tag', "${project.name}:${project.version}",\ - "${project.name}:latest" -} - -task buildLatestImage { - dependsOn buildImage - dependsOn tagLatestImage -} - task pushImage(type: Exec) { dependsOn buildImage + // Don't push without testing first + dependsOn test + def registry = "${project.rootProject.properties['docker.registry']}" commandLine 'podman', 'push', '--tls-verify=false', \ - "localhost/${project.name}:${project.version}", \ - "${project.rootProject.properties['docker.registry']}" \ - + "/${project.name}:${project.version}" + "localhost/${project.name}:${project.gitBranch}", \ + "${registry}/${project.name}:${project.gitBranch}" + + if (!project.version.contains("SNAPSHOT")) { + commandLine 'podman', 'tag', \ + "${registry}/${project.name}:${project.gitBranch}",\ + "${registry}/${project.name}:${project.version}" + } } -task pushLatestImage(type: Exec) { - dependsOn buildLatestImage +task tagAsLatest(type: Exec) { + dependsOn pushImage enabled = !project.version.contains("SNAPSHOT") && !project.version.contains("alpha") \ @@ -92,28 +83,21 @@ task pushLatestImage(type: Exec) { && project.rootProject.properties['docker.registry'] \ == project.rootProject.properties['docker.testRegistry'] - commandLine 'podman', 'push', '--tls-verify=false', \ - "localhost/${project.name}:${project.version}", \ - "${project.rootProject.properties['docker.registry']}" \ - + "/${project.name}:latest" + def registry = "${project.rootProject.properties['docker.registry']}" + commandLine 'podman', 'tag', \ + "${registry}/${project.name}:${project.version}",\ + "${registry}/${project.name}:latest" } task pushForTest(type: Exec) { dependsOn buildImage commandLine 'podman', 'push', '--tls-verify=false', \ - "localhost/${project.name}:${project.version}", \ - "${project.rootProject.properties['docker.registry']}" \ + "localhost/${project.name}:${project.gitBranch}", \ + "${project.rootProject.properties['docker.testRegistry']}" \ + "/${project.name}:test" } -task pushImages { - // Don't push without testing first - dependsOn test - dependsOn pushImage - dependsOn pushLatestImage -} - test { enabled = project.hasProperty("k8s.testCluster") diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle index 82525c6..00bf7ea 100644 --- a/org.jdrupes.vmoperator.runner.qemu/build.gradle +++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle @@ -31,45 +31,34 @@ application { mainClass = 'org.jdrupes.vmoperator.runner.qemu.Runner' } +project.ext.gitBranch = grgit.branch.current.name.replace('/', '-') + task buildArchImage(type: Exec) { dependsOn installDist inputs.files 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch' commandLine 'podman', 'build', '--pull', - '-t', "${project.name}-arch:${project.version}",\ + '-t', "${project.name}-arch:${project.gitBranch}",\ '-f', 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch', '.' } -task tagLatestArchImage(type: Exec) { - dependsOn buildArchImage - - enabled = !project.version.contains("SNAPSHOT") - && !project.version.contains("alpha") \ - && !project.version.contains("beta") \ - || project.rootProject.properties['docker.testRegistry'] \ - && project.rootProject.properties['docker.registry'] \ - == project.rootProject.properties['docker.testRegistry'] - - commandLine 'podman', 'tag', "${project.name}-arch:${project.version}",\ - "${project.name}-arch:latest" -} - -task buildLatestArchImage { - dependsOn buildArchImage - dependsOn tagLatestArchImage -} - task pushArchImage(type: Exec) { dependsOn buildArchImage + def registry = "${project.rootProject.properties['docker.registry']}" commandLine 'podman', 'push', '--tls-verify=false', \ - "localhost/${project.name}-arch:${project.version}", \ - "${project.rootProject.properties['docker.registry']}" \ - + "/${project.name}-arch:${project.version}" + "localhost/${project.name}-arch:${project.gitBranch}", \ + "${registry}/${project.name}-arch:${project.gitBranch}" + + if (!project.version.contains("SNAPSHOT")) { + commandLine 'podman', 'tag', \ + "${registry}/${project.name}-arch:${project.gitBranch}",\ + "${registry}/${project.name}-arch:${project.version}" + } } -task pushArchLatestImage(type: Exec) { - dependsOn buildLatestArchImage +task tagAsLatestArch(type: Exec) { + dependsOn pushArchImage enabled = !project.version.contains("SNAPSHOT") && !project.version.contains("alpha") \ @@ -78,10 +67,10 @@ task pushArchLatestImage(type: Exec) { && project.rootProject.properties['docker.registry'] \ == project.rootProject.properties['docker.testRegistry'] - commandLine 'podman', 'push', '--tls-verify=false', \ - "localhost/${project.name}-arch:${project.version}", \ - "${project.rootProject.properties['docker.registry']}" \ - + "/${project.name}-arch:latest" + def registry = "${project.rootProject.properties['docker.registry']}" + commandLine 'podman', 'tag', \ + "${registry}/${project.name}-arch:${project.version}",\ + "${registry}/${project.name}-arch:latest" } task buildAlpineImage(type: Exec) { @@ -89,40 +78,27 @@ task buildAlpineImage(type: Exec) { inputs.files 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine' commandLine 'podman', 'build', '--pull', - '-t', "${project.name}-alpine:${project.version}",\ + '-t', "${project.name}-alpine:${project.gitBranch}",\ '-f', 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine', '.' } -task tagLatestAlpineImage(type: Exec) { - dependsOn buildAlpineImage - - enabled = !project.version.contains("SNAPSHOT") - && !project.version.contains("alpha") \ - && !project.version.contains("beta") \ - || project.rootProject.properties['docker.testRegistry'] \ - && project.rootProject.properties['docker.registry'] \ - == project.rootProject.properties['docker.testRegistry'] - - commandLine 'podman', 'tag', "${project.name}-alpine:${project.version}",\ - "${project.name}-alpine:latest" -} - -task buildLatestAlpineImage { - dependsOn buildAlpineImage - dependsOn tagLatestAlpineImage -} - task pushAlpineImage(type: Exec) { dependsOn buildAlpineImage + def registry = "${project.rootProject.properties['docker.registry']}" commandLine 'podman', 'push', '--tls-verify=false', \ - "localhost/${project.name}-alpine:${project.version}", \ - "${project.rootProject.properties['docker.registry']}" \ - + "/${project.name}-alpine:${project.version}" + "localhost/${project.name}-alpine:${project.gitBranch}", \ + "${registry}/${project.name}-alpine:${project.gitBranch}" + + if (!project.version.contains("SNAPSHOT")) { + commandLine 'podman', 'tag', \ + "${registry}/${project.name}-alpine:${project.gitBranch}",\ + "${registry}/${project.name}-alpine:${project.version}" + } } -task pushAlpineLatestImage(type: Exec) { - dependsOn buildLatestAlpineImage +task tagAsLatestAlpine(type: Exec) { + dependsOn pushAlpineImage enabled = !project.version.contains("SNAPSHOT") && !project.version.contains("alpha") \ @@ -131,16 +107,19 @@ task pushAlpineLatestImage(type: Exec) { && project.rootProject.properties['docker.registry'] \ == project.rootProject.properties['docker.testRegistry'] - commandLine 'podman', 'push', '--tls-verify=false', \ - "localhost/${project.name}-alpine:${project.version}", \ - "${project.rootProject.properties['docker.registry']}" \ - + "/${project.name}-alpine:latest" + def registry = "${project.rootProject.properties['docker.registry']}" + commandLine 'podman', 'tag', \ + "${registry}/${project.name}-alpine:${project.version}",\ + "${registry}/${project.name}-alpine:latest" } -task pushImages { +task pushImage { dependsOn pushArchImage - dependsOn pushArchLatestImage dependsOn pushAlpineImage - dependsOn pushAlpineLatestImage +} + +task tagAsLatest { + dependsOn tagAsLatestArch + dependsOn tagAsLatestAlpine } diff --git a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch index 6ba8500..0c2fd86 100644 --- a/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch +++ b/org.jdrupes.vmoperator.runner.qemu/src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch @@ -1,4 +1,4 @@ -FROM archlinux/archlinux:latest +FROM docker.io/archlinux/archlinux:latest RUN systemd-firstboot diff --git a/package-lock.json b/package-lock.json index 85f62bb..97aaade 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,129 +27,57 @@ "typescript": "^5.2.2" } }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", - "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", - "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.0", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -164,15 +92,24 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dev": true, "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -180,14 +117,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -195,63 +132,76 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.22.15", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -261,79 +211,80 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", - "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -402,9 +353,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -414,34 +365,34 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -449,13 +400,13 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -478,18 +429,18 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", + "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "peer": true, "dependencies": { @@ -522,9 +473,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "peer": true, "dependencies": { @@ -577,9 +528,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", - "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "peer": true, "engines": { @@ -593,14 +544,15 @@ "dev": true }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "peer": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -646,52 +598,149 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true, "peer": true }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -701,9 +750,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -711,9 +760,9 @@ } }, "node_modules/@jsdoc/salty": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.5.tgz", - "integrity": "sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.8.tgz", + "integrity": "sha512-5e+SFVavj1ORKlKaKr2BmTOekmXbelU7dC0cDkQLqag7xfuTPuGMUFx7KWJuv4bYZrTsoL2Z18VVCOKYxzoHcg==", "dev": true, "dependencies": { "lodash": "^4.17.21" @@ -757,6 +806,83 @@ "node": ">= 8" } }, + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/@npmcli/agent/node_modules/socks-proxy-agent": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/@npmcli/fs": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", @@ -770,39 +896,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/@npmcli/fs/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/fs/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@npmcli/move-file": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", @@ -817,6 +910,16 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/plugin-node-resolve": { "version": "15.2.3", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", @@ -843,9 +946,9 @@ } }, "node_modules/@rollup/plugin-replace": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.5.tgz", - "integrity": "sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.7.tgz", + "integrity": "sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==", "dev": true, "dependencies": { "@rollup/pluginutils": "^5.0.1", @@ -886,9 +989,9 @@ } }, "node_modules/@rollup/pluginutils": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.5.tgz", - "integrity": "sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", "dev": true, "dependencies": { "@types/estree": "^1.0.0", @@ -908,9 +1011,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.1.5.tgz", - "integrity": "sha512-/fwx6GS8cIbM2rTNyLMxjSCOegHywOdXO+kN9yFy018iCULcKZCyA3xvzw4bxyKbYfdSxQgdhbsl0egNcxerQw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", "cpu": [ "arm" ], @@ -921,9 +1024,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.1.5.tgz", - "integrity": "sha512-tmXh7dyEt+JEz/NgDJlB1UeL/1gFV0v8qYzUAU42WZH4lmUJ5rp6/HkR2qUNC5jCgYEwd8/EfbHKtGIEfS4CUg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", "cpu": [ "arm64" ], @@ -934,9 +1037,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.1.5.tgz", - "integrity": "sha512-lTDmLxdEVhzI3KCesZUrNbl3icBvPrDv/85JasY5gh4P2eAuDFmM4uj9HC5DdH0anLC0fwJ+1Uzasr4qOXcjRQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", "cpu": [ "arm64" ], @@ -947,9 +1050,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.1.5.tgz", - "integrity": "sha512-v6qEHZyjWnIgcc4oiy8AIeFsUJAx+Kg0sLj+RE7ICwv3u7YC/+bSClxAiBASRjMzqsq0Z+I/pfxj+OD8mjBYxg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", "cpu": [ "x64" ], @@ -960,9 +1063,22 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.1.5.tgz", - "integrity": "sha512-WngCfwPEDUNbZR1FNO2TCROYUwJvRlbvPi3AS85bDUkkoRDBcjUIz42cuB1j4PKilmnZascL5xTMF/yU8YFayA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", "cpu": [ "arm" ], @@ -973,9 +1089,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.1.5.tgz", - "integrity": "sha512-Q2A/PEP/UTPTOBwgar3mmCaApahoezai/8e/7f4GCLV6XWCpnU4YwkQQtla7d7nUnc792Ps7g1G0WMovzIknrA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", "cpu": [ "arm64" ], @@ -986,9 +1102,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.1.5.tgz", - "integrity": "sha512-84aBKNAVzTU/eG3tb2+kR4NGRAtm2YVW/KHwkGGDR4z1k4hyrDbuImsfs/6J74t6y0YLOe9HOSu7ejRjzUBGVQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", "cpu": [ "arm64" ], @@ -998,10 +1114,49 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.1.5.tgz", - "integrity": "sha512-mldtP9UEBurIq2+GYMdNeiqCLW1fdgf4KdkMR/QegAeXk4jFHkKQl7p0NITrKFVyVqzISGXH5gR6GSTBH4wszw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", "cpu": [ "x64" ], @@ -1012,9 +1167,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.1.5.tgz", - "integrity": "sha512-36p+nMcSxjAEzfU47+by102HolUtf/EfgBAidocTKAofJMTqG5QD50qzaFLk4QO+z7Qvg4qd0wr99jGAwnKOig==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", "cpu": [ "x64" ], @@ -1025,9 +1180,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.1.5.tgz", - "integrity": "sha512-5oxhubo0A3J8aF/tG+6jHBg785HF8/88kl1YnfbDKmnqMxz/EFiAQDH9cq6lbnxofjn8tlq5KiTf0crJGOGThg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", "cpu": [ "arm64" ], @@ -1038,9 +1193,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.1.5.tgz", - "integrity": "sha512-uVQyBREKX9ErofL8KAZ4iVlqzSZOXSIG+BOLYuz5FD+Cg6jh1eLIeUa3Q4SgX0QaTRFeeAgSNqCC+8kZrZBpSw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", "cpu": [ "ia32" ], @@ -1051,9 +1206,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.1.5.tgz", - "integrity": "sha512-FQ5qYqRJ2vUBSom3Fos8o/6UvAMOvlus4+HGCAifH1TagbbwVnVVe0o01J1V52EWnQ8kmfpJDJ0FMrfM5yzcSA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", "cpu": [ "x64" ], @@ -1082,88 +1237,88 @@ } }, "node_modules/@types/debug": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.9.tgz", - "integrity": "sha512-8Hz50m2eoS56ldRlepxSBa6PWEVCtzUo/92HgLc2qTMnotJNIm7xP+UZhyWoYsyOdd5dxZ+NZLb24rsKyFs2ow==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "dev": true, "dependencies": { "@types/ms": "*" } }, "node_modules/@types/estree": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz", - "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, "node_modules/@types/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-CqDQhn7jxaN9zw7zAu926zIx51ZzMaX8U8Wa4jGpKI6jeBr9ejFE68AQ+h+ztfrNJD+leo7K1cLbvMjpHfZSRg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/extend/-/extend-3.0.4.tgz", + "integrity": "sha512-ArMouDUTJEz1SQRpFsT2rIw7DeqICFv5aaVzLSIYMYQSLcwcGOfT3VyglQs/p7K3F7fT4zxr0NWxYZIdifD6dA==", "dev": true }, "node_modules/@types/hast": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.6.tgz", - "integrity": "sha512-47rJE80oqPmFdVDCD7IheXBrVdwuBgsYwoczFvKmwfo2Mzsnt+V9OONsYauFmICb6lQPpCuXYJWejBNs4pDJRg==", + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", "dev": true, "dependencies": { "@types/unist": "^2" } }, "node_modules/@types/json-schema": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", - "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, "node_modules/@types/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-pTjcqY9E4nOI55Wgpz7eiI8+LzdYnw3qxXCfHyBDdPbYvbyLgWLJGh8EdPvqawwMK1Uo1794AUkkR38Fr0g+2g==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", "dev": true }, "node_modules/@types/markdown-it": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", - "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", "dev": true, "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" + "@types/linkify-it": "^5", + "@types/mdurl": "^2" } }, "node_modules/@types/mdast": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.13.tgz", - "integrity": "sha512-HjiGiWedR0DVFkeNljpa6Lv4/IZU1+30VY5d747K7lBudFc3R0Ibr6yJ9lN3BE28VnZyDfLF/VB1Ql1ZIbKrmg==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", "dev": true, "dependencies": { "@types/unist": "^2" } }, "node_modules/@types/mdurl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.3.tgz", - "integrity": "sha512-T5k6kTXak79gwmIOaDF2UUQXFbnBE0zBUzF20pz7wDYu0RQMzWg+Ml/Pz50214NsFHBITkoi5VtdjFZnJ2ijjA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "dev": true }, "node_modules/@types/minimist": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.3.tgz", - "integrity": "sha512-ZYFzrvyWUNhaPomn80dsMNgMeXxNWZBdkuG/hWlUvXvbdUH8ZERNBGXnU87McuGcWDsyzX2aChCv/SVN348k3A==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", "dev": true }, "node_modules/@types/ms": { - "version": "0.7.32", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.32.tgz", - "integrity": "sha512-xPSg0jm4mqgEkNhowKgZFBNtwoEwF6gJ4Dhww+GFpm3IgtNseHQZ5IqdNwnquZEoANxyDAKDRAdVo4Z72VvD/g==", + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", "dev": true }, "node_modules/@types/normalize-package-data": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.2.tgz", - "integrity": "sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true }, "node_modules/@types/parse5": { @@ -1179,34 +1334,34 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, "node_modules/@types/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==", + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.3.tgz", + "integrity": "sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==", "dev": true }, "node_modules/@types/unist": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.8.tgz", - "integrity": "sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz", - "integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/type-utils": "6.9.1", - "@typescript-eslint/utils": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1231,50 +1386,17 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/parser": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", - "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/typescript-estree": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { @@ -1294,13 +1416,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz", - "integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1311,13 +1433,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz", - "integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.9.1", - "@typescript-eslint/utils": "6.9.1", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1338,9 +1460,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz", - "integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1351,16 +1473,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz", - "integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/visitor-keys": "6.9.1", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", + "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, @@ -1377,51 +1500,18 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/utils": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz", - "integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.9.1", - "@typescript-eslint/types": "6.9.1", - "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "engines": { @@ -1435,46 +1525,13 @@ "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz", - "integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1493,84 +1550,74 @@ "peer": true }, "node_modules/@vue/compiler-core": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", - "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.29.tgz", + "integrity": "sha512-TFKiRkKKsRCKvg/jTSSKK7mYLJEQdUiUfykbG49rubC9SfDyvT2JrzTReopWlz2MxqeLyxh9UZhvxEIBgAhtrg==", "dev": true, "dependencies": { - "@babel/parser": "^7.21.3", - "@vue/shared": "3.3.4", + "@babel/parser": "^7.24.7", + "@vue/shared": "3.4.29", + "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", - "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.29.tgz", + "integrity": "sha512-A6+iZ2fKIEGnfPJejdB7b1FlJzgiD+Y/sxxKwJWg1EbJu6ZPgzaPQQ51ESGNv0CP6jm6Z7/pO6Ia8Ze6IKrX7w==", "dev": true, "dependencies": { - "@vue/compiler-core": "3.3.4", - "@vue/shared": "3.3.4" + "@vue/compiler-core": "3.4.29", + "@vue/shared": "3.4.29" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", - "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.29.tgz", + "integrity": "sha512-zygDcEtn8ZimDlrEQyLUovoWgKQic6aEQqRXce2WXBvSeHbEbcAsXyCk9oG33ZkyWH4sl9D3tkYc1idoOkdqZQ==", "dev": true, "dependencies": { - "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.4", - "@vue/compiler-dom": "3.3.4", - "@vue/compiler-ssr": "3.3.4", - "@vue/reactivity-transform": "3.3.4", - "@vue/shared": "3.3.4", + "@babel/parser": "^7.24.7", + "@vue/compiler-core": "3.4.29", + "@vue/compiler-dom": "3.4.29", + "@vue/compiler-ssr": "3.4.29", + "@vue/shared": "3.4.29", "estree-walker": "^2.0.2", - "magic-string": "^0.30.0", - "postcss": "^8.1.10", - "source-map-js": "^1.0.2" + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", - "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.29.tgz", + "integrity": "sha512-rFbwCmxJ16tDp3N8XCx5xSQzjhidYjXllvEcqX/lopkoznlNPz3jyy0WGJCyhAaVQK677WWFt3YO/WUEkMMUFQ==", "dev": true, "dependencies": { - "@vue/compiler-dom": "3.3.4", - "@vue/shared": "3.3.4" - } - }, - "node_modules/@vue/reactivity-transform": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", - "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.4", - "@vue/shared": "3.3.4", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.0" + "@vue/compiler-dom": "3.4.29", + "@vue/shared": "3.4.29" } }, "node_modules/@vue/shared": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", - "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==", + "version": "3.4.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", + "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", "dev": true }, "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1686,25 +1733,6 @@ "node": ">= 8" } }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true - }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "dev": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1755,12 +1783,15 @@ "dev": true }, "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bluebird": { @@ -1785,21 +1816,21 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "dev": true, "funding": [ { @@ -1816,10 +1847,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.16" }, "bin": { "browserslist": "cli.js" @@ -1933,9 +1964,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001547", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz", - "integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==", + "version": "1.0.30001634", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001634.tgz", + "integrity": "sha512-fbBYXQ9q3+yp1q1gBk86tOFs4pyn/yxFm5ZNP18OXJDfA3txImOY9PhfxVggZ4vRHDqoU8NrKU81eN0OtzOgRA==", "dev": true, "funding": [ { @@ -2017,16 +2048,10 @@ } }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -2039,6 +2064,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -2093,15 +2121,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "bin": { - "color-support": "bin.js" - } - }, "node_modules/colord": { "version": "2.9.3", "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", @@ -2148,12 +2167,6 @@ "source-map": "^0.6.1" } }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2341,9 +2354,9 @@ "optional": true }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -2420,12 +2433,6 @@ "node": ">=0.10.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -2436,9 +2443,9 @@ } }, "node_modules/diff": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", - "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, "engines": { "node": ">=0.3.1" @@ -2482,9 +2489,9 @@ } }, "node_modules/documentation": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.2.tgz", - "integrity": "sha512-hWoTf8/u4pOjib02L7w94hwmhPfcSwyJNGtlPdGVe8GFyq8HkzcFzQQltaaikKunHEp0YSwDAbwBAO7nxrWIfA==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.3.tgz", + "integrity": "sha512-B7cAviVKN9Rw7Ofd+9grhVuxiHwly6Ieh+d/ceMw8UdBOv/irkuwnDEJP8tq0wgdLJDUVuIkovV+AX9mTrZFxg==", "dev": true, "dependencies": { "@babel/core": "^7.18.10", @@ -2550,6 +2557,15 @@ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", @@ -2598,9 +2614,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.553", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.553.tgz", - "integrity": "sha512-HiRdtyKS2+VhiXvjhMvvxiMC33FJJqTA5EB2YHgFZW6v7HkK4Q9Ahv2V7O2ZPgAjw+MyCJVMQvigj13H8t+wvA==", + "version": "1.4.803", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.803.tgz", + "integrity": "sha512-61H9mLzGOCLLVsnLiRzCbc63uldP0AniRYPV3hbGVtONA1pI7qSGILdbofR7A8TMbOypDocEAjH/e+9k1QIe3g==", "dev": true }, "node_modules/emoji-regex": { @@ -2620,10 +2636,13 @@ } }, "node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "engines": { + "node": ">=0.12" + }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -2653,35 +2672,39 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true, "engines": { "node": ">=6" } }, "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "peer": true, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint": { - "version": "8.52.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", - "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.52.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -2783,36 +2806,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "peer": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2827,9 +2820,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "peer": true, "dependencies": { @@ -2852,22 +2845,6 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "peer": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2881,48 +2858,6 @@ "node": "*" } }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "peer": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "peer": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -2949,19 +2884,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -3037,6 +2959,12 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "dev": true + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -3051,9 +2979,9 @@ "peer": true }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -3081,9 +3009,9 @@ "peer": true }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -3103,9 +3031,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -3132,25 +3060,26 @@ } }, "node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "peer": true, "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat-cache": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz", - "integrity": "sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "peer": true, "dependencies": { @@ -3159,16 +3088,32 @@ "rimraf": "^3.0.2" }, "engines": { - "node": ">=12.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true, "peer": true }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -3215,23 +3160,13 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/gaze": { @@ -3293,9 +3228,9 @@ } }, "node_modules/git-url-parse": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz", - "integrity": "sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.1.tgz", + "integrity": "sha512-PCFJyeSSdtnbfhSNRw9Wk96dDCNx+sogTe4YNXeXSJxt7xz5hvXekuRn9JX7m+Mf4OscCu8h+mtAl3+h5Fo8lQ==", "dev": true, "dependencies": { "git-up": "^7.0.0" @@ -3308,19 +3243,18 @@ "dev": true }, "node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" }, "engines": { - "node": ">=12" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3338,6 +3272,30 @@ "node": ">= 6" } }, + "node_modules/glob/node_modules/minimatch": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -3397,26 +3355,6 @@ "concat-map": "0.0.1" } }, - "node_modules/globule/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/globule/node_modules/minimatch": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", @@ -3450,15 +3388,6 @@ "node": ">=6" } }, - "node_modules/has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", - "dev": true, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -3468,18 +3397,24 @@ "node": ">=4" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true - }, "node_modules/hash-sum": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", "dev": true }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hast-util-from-parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.2.tgz", @@ -3749,18 +3684,18 @@ } }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, "engines": { "node": ">= 4" } }, "node_modules/immutable": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", - "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", + "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", "dev": true }, "node_modules/import-cwd": { @@ -3792,16 +3727,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/import-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", @@ -3814,6 +3739,15 @@ "node": ">=8" } }, + "node_modules/import-from/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3838,16 +3772,6 @@ "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", "dev": true }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -3872,11 +3796,18 @@ "node": ">= 0.10" } }, - "node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", - "dev": true + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } }, "node_modules/is-absolute": { "version": "1.0.0", @@ -3948,12 +3879,12 @@ } }, "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4083,6 +4014,24 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/jackspeak": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", + "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/js-base64": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", @@ -4116,22 +4065,28 @@ "xmlcreate": "^2.0.4" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, "node_modules/jsdoc": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", - "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.3.tgz", + "integrity": "sha512-Nu7Sf35kXJ1MWDZIMAuATRQTg1iIPdzh7tqJ6jjvaU/GfDf+qi5UV8zJR3Mo+/pYFvm8mzay4+6O5EWigaQBQw==", "dev": true, "dependencies": { "@babel/parser": "^7.20.15", "@jsdoc/salty": "^0.2.1", - "@types/markdown-it": "^12.2.3", + "@types/markdown-it": "^14.1.1", "bluebird": "^3.7.2", "catharsis": "^0.9.0", "escape-string-regexp": "^2.0.0", "js2xmlparser": "^4.0.2", "klaw": "^3.0.0", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", "marked": "^4.0.10", "mkdirp": "^1.0.4", "requizzle": "^0.2.3", @@ -4145,6 +4100,15 @@ "node": ">=12.0.0" } }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/jsdoc/node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4209,9 +4173,9 @@ } }, "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, "node_modules/jsonfile": { @@ -4303,33 +4267,34 @@ "dev": true }, "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, "dependencies": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "node_modules/loader-utils": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", - "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", "dev": true, "engines": { "node": ">= 12.13.0" } }, "node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "peer": true, "dependencies": { - "p-locate": "^6.0.0" + "p-locate": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4392,15 +4357,12 @@ "dev": true }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" } }, "node_modules/make-dir": { @@ -4418,6 +4380,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/make-fetch-happen": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", @@ -4476,19 +4447,20 @@ } }, "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, "dependencies": { "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, "bin": { - "markdown-it": "bin/markdown-it.js" + "markdown-it": "bin/markdown-it.mjs" } }, "node_modules/markdown-it-anchor": { @@ -4828,9 +4800,9 @@ "dev": true }, "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true }, "node_modules/meow": { @@ -4917,15 +4889,6 @@ "node": ">=8" } }, - "node_modules/meow/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/meow/node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -5570,12 +5533,12 @@ ] }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -5592,15 +5555,18 @@ } }, "node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist-options": { @@ -5747,15 +5713,15 @@ "dev": true }, "node_modules/nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", "dev": true }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -5786,152 +5752,359 @@ } }, "node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.1.0.tgz", + "integrity": "sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==", "dev": true, "dependencies": { "env-paths": "^2.2.0", - "glob": "^7.1.4", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", "semver": "^7.3.5", "tar": "^6.1.2", - "which": "^2.0.2" + "which": "^4.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": ">= 10.12.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/node-gyp/node_modules/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", "dev": true, "dependencies": { - "@gar/promisify": "^1.0.1", "semver": "^7.3.5" - } - }, - "node_modules/node-gyp/node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" }, "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/node-gyp/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/node-gyp/node_modules/cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "version": "18.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.3.tgz", + "integrity": "sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg==", "dev": true, "dependencies": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", + "minipass-pipeline": "^1.2.4", "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" }, "engines": { - "node": ">= 10" + "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/node-gyp/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/node-gyp/node_modules/cacache/node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/node-gyp/node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "node_modules/node-gyp/node_modules/cacache/node_modules/glob/node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-gyp/node_modules/cacache/node_modules/minimatch": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", "dev": true, "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 6" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" } }, "node_modules/node-gyp/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, "engines": { - "node": ">=10" + "node": "14 || >=16.14" } }, "node_modules/node-gyp/node_modules/make-fetch-happen": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "dev": true, + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/node_modules/make-fetch-happen/node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/node-gyp/node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/node-gyp/node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/node-gyp/node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/node-sass": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-9.0.0.tgz", + "integrity": "sha512-yltEuuLrfH6M7Pq2gAj5B6Zm7m+gdZoG66wTqG6mIZV/zijq3M2OO2HswtT6oBspPyFhHDcaxWpsBm0fRNDHPg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "async-foreach": "^0.1.3", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "lodash": "^4.17.15", + "make-fetch-happen": "^10.0.4", + "meow": "^9.0.0", + "nan": "^2.17.0", + "node-gyp": "^10.1.0", + "sass-graph": "^4.0.1", + "stdout-stream": "^1.4.0", + "true-case-path": "^2.2.1" + }, + "bin": { + "node-sass": "bin/node-sass" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/node-sass/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/node-sass/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-sass/node_modules/node-gyp/node_modules/make-fetch-happen": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "dev": true, + "extraneous": true, "dependencies": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", @@ -5954,202 +6127,6 @@ "node": ">= 10" } }, - "node_modules/node-gyp/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/node-gyp/node_modules/minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "dev": true, - "dependencies": { - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" - } - }, - "node_modules/node-gyp/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "dev": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/node-gyp/node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/node-gyp/node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/node-gyp/node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/node-gyp/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true - }, - "node_modules/node-sass": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-9.0.0.tgz", - "integrity": "sha512-yltEuuLrfH6M7Pq2gAj5B6Zm7m+gdZoG66wTqG6mIZV/zijq3M2OO2HswtT6oBspPyFhHDcaxWpsBm0fRNDHPg==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "async-foreach": "^0.1.3", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "gaze": "^1.0.0", - "get-stdin": "^4.0.1", - "glob": "^7.0.3", - "lodash": "^4.17.15", - "make-fetch-happen": "^10.0.4", - "meow": "^9.0.0", - "nan": "^2.17.0", - "node-gyp": "^8.4.1", - "sass-graph": "^4.0.1", - "stdout-stream": "^1.4.0", - "true-case-path": "^2.2.1" - }, - "bin": { - "node-sass": "bin/node-sass" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/node-sass/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/node-sass/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/node-sass/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/node-sass/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-sass/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/node-sass/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6163,18 +6140,18 @@ } }, "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", "dev": true, "dependencies": { - "abbrev": "1" + "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": ">=6" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/normalize-package-data": { @@ -6192,39 +6169,6 @@ "node": ">=10" } }, - "node_modules/normalize-package-data/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-package-data/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -6326,6 +6270,11 @@ "write-file-atomic" ], "dev": true, + "workspaces": [ + "docs", + "smoke-tests", + "workspaces/*" + ], "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/arborist": "^5.6.3", @@ -6370,7 +6319,7 @@ "mkdirp": "^1.0.4", "mkdirp-infer-owner": "^2.0.0", "ms": "^2.1.2", - "node-gyp": "^9.1.0", + "node-gyp": "^10.1.0", "nopt": "^6.0.0", "npm-audit-report": "^3.0.0", "npm-install-checks": "^5.0.0", @@ -8812,21 +8761,6 @@ "inBundle": true, "license": "ISC" }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "dev": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -8839,28 +8773,19 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "peer": true, "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -8876,30 +8801,32 @@ } }, "node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "peer": true, "dependencies": { - "yocto-queue": "^1.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "peer": true, "dependencies": { - "p-limit": "^4.0.0" + "p-limit": "^3.0.2" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9027,21 +8954,12 @@ "dev": true }, "node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/path-key": { @@ -9080,6 +8998,40 @@ "node": ">=0.10.0" } }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -9090,9 +9042,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, "node_modules/picomatch": { @@ -9183,19 +9135,10 @@ "node": ">=8" } }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -9212,9 +9155,9 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -9462,9 +9405,9 @@ } }, "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "engines": { "node": "^10 || ^12 || >= 14" @@ -9474,9 +9417,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", - "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", "dev": true, "dependencies": { "icss-utils": "^5.0.0", @@ -9491,9 +9434,9 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.4" @@ -9702,9 +9645,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", + "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -9761,6 +9704,15 @@ "node": ">= 0.8.0" } }, + "node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -9796,9 +9748,9 @@ } }, "node_modules/property-information": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.3.0.tgz", - "integrity": "sha512-gVNZ74nqhRMiIUYWGQdosYetaKc83x8oT41a0LlV3AAFCAZwCpg4vmGkq8t34+cUhp3cnM4XDiU/7xlgK7HGrg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", "dev": true, "funding": { "type": "github", @@ -9821,6 +9773,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9894,6 +9855,76 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/read-pkg-up/node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", @@ -9906,6 +9937,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/read-pkg-up/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/read-pkg/node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", @@ -9918,20 +9961,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -10102,12 +10131,13 @@ } }, "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "peer": true, "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/retry": { @@ -10133,6 +10163,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -10144,53 +10175,14 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/rollup": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.1.5.tgz", - "integrity": "sha512-AEw14/q4NHYQkQlngoSae2yi7hDBeT9w84aEzdgCr39+2RL+iTG84lGTkgC1Wp5igtquN64cNzuzZKVz+U6jOg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, "bin": { "rollup": "dist/bin/rollup" }, @@ -10199,18 +10191,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.1.5", - "@rollup/rollup-android-arm64": "4.1.5", - "@rollup/rollup-darwin-arm64": "4.1.5", - "@rollup/rollup-darwin-x64": "4.1.5", - "@rollup/rollup-linux-arm-gnueabihf": "4.1.5", - "@rollup/rollup-linux-arm64-gnu": "4.1.5", - "@rollup/rollup-linux-arm64-musl": "4.1.5", - "@rollup/rollup-linux-x64-gnu": "4.1.5", - "@rollup/rollup-linux-x64-musl": "4.1.5", - "@rollup/rollup-win32-arm64-msvc": "4.1.5", - "@rollup/rollup-win32-ia32-msvc": "4.1.5", - "@rollup/rollup-win32-x64-msvc": "4.1.5", + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", "fsevents": "~2.3.2" } }, @@ -10329,39 +10325,6 @@ "node": ">= 8.0.0" } }, - "node_modules/rollup-plugin-typescript2/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/rollup-plugin-typescript2/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/rollup-plugin-typescript2/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/rollup-plugin-vue": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/rollup-plugin-vue/-/rollup-plugin-vue-6.0.0.tgz", @@ -10460,9 +10423,9 @@ "optional": true }, "node_modules/sass": { - "version": "1.69.5", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", - "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", + "version": "1.77.5", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.5.tgz", + "integrity": "sha512-oDfX1mukIlxacPdQqNb6mV2tVCrnE+P3nVYioy72V5tlk56CPNcO4TCuFcaCRKKfJ1M3lH95CleRS+dVKL2qMg==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -10494,48 +10457,6 @@ "node": ">=12" } }, - "node_modules/sass-graph/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/sass-graph/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sass-graph/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/scss-tokenizer": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz", @@ -10556,29 +10477,26 @@ } }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -10601,9 +10519,9 @@ } }, "node_modules/shiki": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", - "integrity": "sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==", + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", + "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", "dev": true, "dependencies": { "ansi-sequence-parser": "^1.1.0", @@ -10613,10 +10531,16 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/slash": { "version": "3.0.0", @@ -10638,22 +10562,22 @@ } }, "node_modules/smob": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz", - "integrity": "sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", "dev": true }, "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", "dev": true, "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, @@ -10681,9 +10605,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -10720,9 +10644,9 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true }, "node_modules/spdx-expression-parse": { @@ -10736,9 +10660,15 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", - "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "dev": true }, "node_modules/ssri": { @@ -10799,15 +10729,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/string-hash": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", @@ -10828,10 +10749,25 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/stringify-entities": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", - "integrity": "sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "dev": true, "dependencies": { "character-entities-html4": "^2.0.0", @@ -10854,6 +10790,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -10946,9 +10895,9 @@ } }, "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, "dependencies": { "chownr": "^2.0.0", @@ -10978,9 +10927,9 @@ "dev": true }, "node_modules/terser": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.23.0.tgz", - "integrity": "sha512-Iyy83LN0uX9ZZLCX4Qbu5JiHiWjOCTwrmM9InWOzVeM++KNWEsqV4YgN9U9E8AlohQ6Gs42ztczlWOG/lwDAMA==", + "version": "5.31.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.1.tgz", + "integrity": "sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -11049,9 +10998,9 @@ } }, "node_modules/trough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz", - "integrity": "sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "dev": true, "funding": { "type": "github", @@ -11065,21 +11014,21 @@ "dev": true }, "node_modules/ts-api-utils": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", - "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", "dev": true, "engines": { - "node": ">=16.13.0" + "node": ">=16" }, "peerDependencies": { "typescript": ">=4.2.0" } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "dev": true }, "node_modules/type-check": { @@ -11108,15 +11057,15 @@ } }, "node_modules/typedoc": { - "version": "0.25.3", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.3.tgz", - "integrity": "sha512-Ow8Bo7uY1Lwy7GTmphRIMEo6IOZ+yYUyrc8n5KXIZg1svpqhZSWgni2ZrDhe+wLosFS8yswowUzljTAV/3jmWw==", + "version": "0.25.13", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.13.tgz", + "integrity": "sha512-pQqiwiJ+Z4pigfOnnysObszLiU3mVLWAExSPf+Mu06G/qsc3wzbuM56SZQvONhHLncLUhYzOVkjFFpFfL5AzhQ==", "dev": true, "dependencies": { "lunr": "^2.3.9", "marked": "^4.3.0", "minimatch": "^9.0.3", - "shiki": "^0.14.1" + "shiki": "^0.14.7" }, "bin": { "typedoc": "bin/typedoc" @@ -11125,37 +11074,22 @@ "node": ">= 16" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x" + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x" } }, "node_modules/typedoc-plugin-missing-exports": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-2.1.0.tgz", - "integrity": "sha512-+1DhqZCEu7Vu5APnrqpPwl31D+hXpt1fV0Le9ycCRL1eLVdatdl6KVt4SEVwPxnEpKwgOn2dNX6I9+0F1aO2aA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/typedoc-plugin-missing-exports/-/typedoc-plugin-missing-exports-2.3.0.tgz", + "integrity": "sha512-iI9ITNNLlbsLCBBeYDyu0Qqp3GN/9AGyWNKg8bctRXuZEPT7G1L+0+MNWG9MsHcf/BFmNbXL0nQ8mC/tXRicog==", "dev": true, "peerDependencies": { "typedoc": "0.24.x || 0.25.x" } }, - "node_modules/typedoc/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -11166,9 +11100,9 @@ } }, "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "dev": true }, "node_modules/unc-path-regex": { @@ -11333,18 +11267,18 @@ } }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "engines": { "node": ">= 10.0.0" } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", "dev": true, "funding": [ { @@ -11361,8 +11295,8 @@ } ], "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -11570,9 +11504,9 @@ "dev": true }, "node_modules/vue-template-compiler": { - "version": "2.7.14", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz", - "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==", + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", "dev": true, "optional": true, "dependencies": { @@ -11605,13 +11539,14 @@ "node": ">= 8" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" + "peer": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/wrap-ansi": { @@ -11631,11 +11566,23 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } }, "node_modules/xmlcreate": { "version": "2.0.4", @@ -11704,12 +11651,13 @@ } }, "node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "peer": true, "engines": { - "node": ">=12.20" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/package.json b/package.json index 5d4c1f3..329bcd5 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,10 @@ "typedoc-plugin-missing-exports": "^2.1.0", "typescript": "^5.2.2" }, + "overrides": { + "node-gyp": "^10.1.0", + "glob": "^9.0.0" + }, "eslintConfig": { "root": true, "extends": "eslint:recommended", diff --git a/spice-squid/Containerfile b/spice-squid/Containerfile index b65b631..5c94829 100644 --- a/spice-squid/Containerfile +++ b/spice-squid/Containerfile @@ -1,4 +1,4 @@ -FROM alpine:3.19 +FROM docker.io/alpine:3.19 RUN apk update &&\ apk add --no-cache inotify-tools &&\ From 92f4b3aaf83327f78743511a08d5cd5b5bb99409 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 18 Jun 2024 18:03:14 +0200 Subject: [PATCH 148/462] Avoid unnecessary pages runs. --- .gitlab-ci.yml | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f1e59d7..a8673aa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -55,18 +55,22 @@ publish-images: test-pages: stage: test extends: .pages-job + rules: + - if: $CI_COMMIT_BRANCH == "gh-pages" script: - bundle exec jekyll build -d test artifacts: paths: - test -publish-pages: - stage: publish - extends: .pages-job - script: - - bundle exec jekyll build -d public - artifacts: - paths: - - public - environment: production +#publish-pages: +# stage: publish +# extends: .pages-job +# rules: +# - if: $CI_COMMIT_BRANCH == "gh-pages" +# script: +# - bundle exec jekyll build -d public +# artifacts: +# paths: +# - public +# environment: production From 98ce74f42b30795edc9c33de2c1e6072865c83db Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 18 Jun 2024 21:32:01 +0200 Subject: [PATCH 149/462] Adjust link. --- buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle index 081b7d9..8e8413e 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle @@ -67,7 +67,7 @@ task apidocs (type: JavaExec) { '-use', '-linksource', '-link', 'https://docs.oracle.com/en/java/javase/21/docs/api/', - '-link', 'https://mnlipp.github.io/jgrapes/latest-release/javadoc/', + '-link', 'https://jgrapes.org/latest-release/javadoc/', '-link', 'https://freemarker.apache.org/docs/api/', '--add-exports', 'jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED', '--add-exports', 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', From f0ccb83b39fb23ae743bafb34be66bb518ed5784 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 20 Jun 2024 20:41:33 +0200 Subject: [PATCH 150/462] Move pages. --- webpages/.gitignore | 4 + webpages/Gemfile | 5 + webpages/_config.yml | 10 + webpages/_includes/matomo.html | 23 ++ webpages/_includes/toc.html | 96 +++++++ webpages/_layouts/vm-operator.html | 71 +++++ webpages/stylesheets/pygment_trac.css | 69 +++++ webpages/stylesheets/styles.css | 244 ++++++++++++++++++ webpages/vm-operator/02_2_operator.png | Bin 0 -> 109024 bytes .../vm-operator/VM-Operator-GUI-preview.png | Bin 0 -> 40113 bytes webpages/vm-operator/VM-Operator-GUI-view.png | Bin 0 -> 42919 bytes .../vm-operator/VM-Operator-with-font.svg | 173 +++++++++++++ webpages/vm-operator/VM-Operator.svg | 184 +++++++++++++ webpages/vm-operator/VmViewer-preview.png | Bin 0 -> 13757 bytes webpages/vm-operator/admin-gui.md | 18 ++ webpages/vm-operator/controller.md | 226 ++++++++++++++++ webpages/vm-operator/favicon.svg | 88 +++++++ webpages/vm-operator/index.md | 60 +++++ webpages/vm-operator/manager.md | 150 +++++++++++ webpages/vm-operator/runner.md | 108 ++++++++ webpages/vm-operator/upgrading.md | 29 +++ webpages/vm-operator/user-gui.md | 143 ++++++++++ webpages/vm-operator/webgui.md | 117 +++++++++ 23 files changed, 1818 insertions(+) create mode 100644 webpages/.gitignore create mode 100644 webpages/Gemfile create mode 100644 webpages/_config.yml create mode 100644 webpages/_includes/matomo.html create mode 100644 webpages/_includes/toc.html create mode 100644 webpages/_layouts/vm-operator.html create mode 100644 webpages/stylesheets/pygment_trac.css create mode 100644 webpages/stylesheets/styles.css create mode 100644 webpages/vm-operator/02_2_operator.png create mode 100644 webpages/vm-operator/VM-Operator-GUI-preview.png create mode 100644 webpages/vm-operator/VM-Operator-GUI-view.png create mode 100644 webpages/vm-operator/VM-Operator-with-font.svg create mode 100644 webpages/vm-operator/VM-Operator.svg create mode 100644 webpages/vm-operator/VmViewer-preview.png create mode 100644 webpages/vm-operator/admin-gui.md create mode 100644 webpages/vm-operator/controller.md create mode 100644 webpages/vm-operator/favicon.svg create mode 100644 webpages/vm-operator/index.md create mode 100644 webpages/vm-operator/manager.md create mode 100644 webpages/vm-operator/runner.md create mode 100644 webpages/vm-operator/upgrading.md create mode 100644 webpages/vm-operator/user-gui.md create mode 100644 webpages/vm-operator/webgui.md diff --git a/webpages/.gitignore b/webpages/.gitignore new file mode 100644 index 0000000..7615a9d --- /dev/null +++ b/webpages/.gitignore @@ -0,0 +1,4 @@ +_site +Gemfile.lock +.bundle +.jekyll-cache \ No newline at end of file diff --git a/webpages/Gemfile b/webpages/Gemfile new file mode 100644 index 0000000..ecbbb7d --- /dev/null +++ b/webpages/Gemfile @@ -0,0 +1,5 @@ +source 'https://rubygems.org' +# gem 'github-pages', group: :jekyll_plugins +gem "jekyll", "~> 4.0" +gem "jekyll-seo-tag" +gem 'webrick', '~> 1.3', '>= 1.3.1' diff --git a/webpages/_config.yml b/webpages/_config.yml new file mode 100644 index 0000000..39ca339 --- /dev/null +++ b/webpages/_config.yml @@ -0,0 +1,10 @@ +plugins: + - jekyll-seo-tag + +author: Michael N. Lipp + +logo: VM-Operator.svg + +tagline: VM-Operator by mnlipp + +description: A Kubernetes operator for running virtual machines (notably Qemu VMs) as pods. diff --git a/webpages/_includes/matomo.html b/webpages/_includes/matomo.html new file mode 100644 index 0000000..3a93186 --- /dev/null +++ b/webpages/_includes/matomo.html @@ -0,0 +1,23 @@ + + + + + diff --git a/webpages/_includes/toc.html b/webpages/_includes/toc.html new file mode 100644 index 0000000..56ac8e4 --- /dev/null +++ b/webpages/_includes/toc.html @@ -0,0 +1,96 @@ +{% capture tocWorkspace %} + {% comment %} + Version 1.0.10 + https://github.com/allejo/jekyll-toc + + "...like all things liquid - where there's a will, and ~36 hours to spare, there's usually a/some way" ~jaybe + + Usage: + {% include toc.html html=content sanitize=true class="inline_toc" id="my_toc" h_min=2 h_max=3 %} + + Parameters: + * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll + + Optional Parameters: + * sanitize (bool) : false - when set to true, the headers will be stripped of any HTML in the TOC + * class (string) : '' - a CSS class assigned to the TOC + * id (string) : '' - an ID to assigned to the TOC + * h_min (int) : 1 - the minimum TOC header level to use; any header lower than this value will be ignored + * h_max (int) : 6 - the maximum TOC header level to use; any header greater than this value will be ignored + * ordered (bool) : false - when set to true, an ordered list will be outputted instead of an unordered list + * item_class (string) : '' - add custom class(es) for each list item; has support for '%level%' placeholder, which is the current heading level + * baseurl (string) : '' - add a base url to the TOC links for when your TOC is on another page than the actual content + * anchor_class (string) : '' - add custom class(es) for each anchor element + + Output: + An ordered or unordered list representing the table of contents of a markdown block. This snippet will only + generate the table of contents and will NOT output the markdown given to it + {% endcomment %} + + {% capture my_toc %}{% endcapture %} + {% assign orderedList = include.ordered | default: false %} + {% assign minHeader = include.h_min | default: 1 %} + {% assign maxHeader = include.h_max | default: 6 %} + {% assign nodes = include.html | split: ' maxHeader %} + {% continue %} + {% endif %} + + {% if firstHeader %} + {% assign firstHeader = false %} + {% assign minHeader = headerLevel %} + {% endif %} + + {% assign indentAmount = headerLevel | minus: minHeader %} + {% assign _workspace = node | split: '' | first }}>{% endcapture %} + {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %} + + {% assign space = '' %} + {% for i in (1..indentAmount) %} + {% assign space = space | prepend: ' ' %} + {% endfor %} + + {% if include.item_class and include.item_class != blank %} + {% capture listItemClass %}{:.{{ include.item_class | replace: '%level%', headerLevel }}}{% endcapture %} + {% endif %} + + {% capture heading_body %}{% if include.sanitize %}{{ header | strip_html }}{% else %}{{ header }}{% endif %}{% endcapture %} + {% capture my_toc %}{{ my_toc }} +{{ space }}{{ listModifier }} {{ listItemClass }} [{{ heading_body | replace: "|", "\|" }}]({% if include.baseurl %}{{ include.baseurl }}{% endif %}#{{ html_id }}){% if include.anchor_class %}{:.{{ include.anchor_class }}}{% endif %}{% endcapture %} + {% endfor %} + + {% if include.class and include.class != blank %} + {% capture my_toc %}{:.{{ include.class }}} +{{ my_toc | lstrip }}{% endcapture %} + {% endif %} + + {% if include.id %} + {% capture my_toc %}{: #{{ include.id }}} +{{ my_toc | lstrip }}{% endcapture %} + {% endif %} +{% endcapture %}{% assign tocWorkspace = '' %}{{ my_toc | markdownify | strip }} diff --git a/webpages/_layouts/vm-operator.html b/webpages/_layouts/vm-operator.html new file mode 100644 index 0000000..dfe4220 --- /dev/null +++ b/webpages/_layouts/vm-operator.html @@ -0,0 +1,71 @@ + + + + + + + + + + + + {% seo %} + + +

+ + {% include matomo.html %} + + + diff --git a/webpages/stylesheets/pygment_trac.css b/webpages/stylesheets/pygment_trac.css new file mode 100644 index 0000000..c6a6452 --- /dev/null +++ b/webpages/stylesheets/pygment_trac.css @@ -0,0 +1,69 @@ +.highlight { background: #ffffff; } +.highlight .c { color: #999988; font-style: italic } /* Comment */ +.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ +.highlight .k { font-weight: bold } /* Keyword */ +.highlight .o { font-weight: bold } /* Operator */ +.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ +.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ +.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #aa0000 } /* Generic.Error */ +.highlight .gh { color: #999999 } /* Generic.Heading */ +.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ +.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ +.highlight .go { color: #888888 } /* Generic.Output */ +.highlight .gp { color: #555555 } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */ +.highlight .gt { color: #aa0000 } /* Generic.Traceback */ +.highlight .kc { font-weight: bold } /* Keyword.Constant */ +.highlight .kd { font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { font-weight: bold } /* Keyword.Pseudo */ +.highlight .kr { font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */ +.highlight .m { color: #009999 } /* Literal.Number */ +.highlight .s { color: #d14 } /* Literal.String */ +.highlight .na { color: #008080 } /* Name.Attribute */ +.highlight .nb { color: #0086B3 } /* Name.Builtin */ +.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */ +.highlight .no { color: #008080 } /* Name.Constant */ +.highlight .ni { color: #800080 } /* Name.Entity */ +.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */ +.highlight .nn { color: #555555 } /* Name.Namespace */ +.highlight .nt { color: #000080 } /* Name.Tag */ +.highlight .nv { color: #008080 } /* Name.Variable */ +.highlight .ow { font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mf { color: #009999 } /* Literal.Number.Float */ +.highlight .mh { color: #009999 } /* Literal.Number.Hex */ +.highlight .mi { color: #009999 } /* Literal.Number.Integer */ +.highlight .mo { color: #009999 } /* Literal.Number.Oct */ +.highlight .sb { color: #d14 } /* Literal.String.Backtick */ +.highlight .sc { color: #d14 } /* Literal.String.Char */ +.highlight .sd { color: #d14 } /* Literal.String.Doc */ +.highlight .s2 { color: #d14 } /* Literal.String.Double */ +.highlight .se { color: #d14 } /* Literal.String.Escape */ +.highlight .sh { color: #d14 } /* Literal.String.Heredoc */ +.highlight .si { color: #d14 } /* Literal.String.Interpol */ +.highlight .sx { color: #d14 } /* Literal.String.Other */ +.highlight .sr { color: #009926 } /* Literal.String.Regex */ +.highlight .s1 { color: #d14 } /* Literal.String.Single */ +.highlight .ss { color: #990073 } /* Literal.String.Symbol */ +.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */ +.highlight .vc { color: #008080 } /* Name.Variable.Class */ +.highlight .vg { color: #008080 } /* Name.Variable.Global */ +.highlight .vi { color: #008080 } /* Name.Variable.Instance */ +.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ + +.type-csharp .highlight .k { color: #0000FF } +.type-csharp .highlight .kt { color: #0000FF } +.type-csharp .highlight .nf { color: #000000; font-weight: normal } +.type-csharp .highlight .nc { color: #2B91AF } +.type-csharp .highlight .nn { color: #000000 } +.type-csharp .highlight .s { color: #A31515 } +.type-csharp .highlight .sc { color: #A31515 } diff --git a/webpages/stylesheets/styles.css b/webpages/stylesheets/styles.css new file mode 100644 index 0000000..748ffcb --- /dev/null +++ b/webpages/stylesheets/styles.css @@ -0,0 +1,244 @@ +body { + background-color: #fff; + padding:50px; + font: normal 16px/1.5 Verdana, Arial, Helvetica, sans-serif; + color:#595959; +} + +h1, h2, h3, h4, h5, h6 { + color:#222; + margin:0 0 20px; +} + +p, ul, ol, table, pre, dl { + margin:0 0 20px; +} + +h1, h2, h3 { + line-height:1.1; +} + +h1 { + font-size:28px; + font-weight: 500; +} + +h2 { + color:#393939; + font-weight: 500; +} + +h3, h4, h5, h6 { + color:#494949; + font-weight: 500; +} + +a { + color:#39c; + text-decoration:none; +} + +a:hover { + color:#069; +} + +a small { + font-size:11px; + color:#777; + margin-top:-0.3em; + display:block; +} + +a:hover small { + color:#777; +} + +.wrapper { + /* width:860px; */ + width: 100%; + margin:0 auto; +} + +blockquote { + border-left:1px solid #e5e5e5; + margin:0; + padding:0 0 0 20px; + font-style:italic; +} + +code, pre { + font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal, Consolas, Liberation Mono, DejaVu Sans Mono, Courier New, monospace; + color:#333; +} + +pre { + font-size: 15px; + padding:8px 15px; + background: #f8f8f8; + border-radius:5px; + border:1px solid #e5e5e5; + overflow-x: auto; +} + +a code { + color: inherit; +} + +table { + width:100%; + border-collapse:collapse; +} + +th, td { + text-align:left; + padding:5px 10px; + border-bottom:1px solid #e5e5e5; +} + +dt { + color:#444; + font-weight:500; +} + +th { + color:#444; +} + +img { + max-width:100%; +} + +header { + /* width:270px; */ + width:calc(29% - 50px); + height:calc(100% - 160px); + overflow: auto; + float:left; + position:fixed; + -webkit-font-smoothing:subpixel-antialiased; +} + +header li { + list-style-type: disc; +} + +header ul { + padding-left: 1rem; +} + +header ul > li { + margin-left: 1rem; +} + +ul.no-bullets { + padding-left: 0; +} + +ul.no-bullets > li { + list-style: none; +} + +strong { + color:#222; + font-weight:500; +} + +section { + width:70%; + max-width:54em; + float:right; + padding-bottom:50px; +} + +small { + font-size:11px; +} + +hr { + border:0; + background:#e5e5e5; + height:1px; + margin:0 0 20px; +} + +footer { + /* width:270px; */ + width:calc(24% - 50px); + height:40px; + float:left; + position:fixed; + padding:30px 0; + bottom:0px; + background-color:white; + -webkit-font-smoothing:subpixel-antialiased; +} + +.post-date { + float: right; +} + +.part-list-title { + margin-bottom:5px; +} + +.part-entry { + margin-bottom:5px; +} + +@media print, screen and (max-width: 960px) { + + div.wrapper { + width:auto; + margin:0; + } + + header, section, footer { + float:none; + position:static; + width:auto; + } + + header { + padding-right:320px; + } + + section { + border:1px solid #e5e5e5; + border-width:1px 0; + padding:20px 0; + margin:0 0 20px; + } + + header a small { + display:inline; + } +} + +@media print, screen and (max-width: 720px) { + body { + word-wrap:break-word; + } + + header { + padding:0; + } + + pre, code { + word-wrap:normal; + } +} + +@media print, screen and (max-width: 480px) { + body { + padding:15px; + } + +} + +@media print { + body { + padding:0.4in; + font-size:12pt; + color:#444; + } +} diff --git a/webpages/vm-operator/02_2_operator.png b/webpages/vm-operator/02_2_operator.png new file mode 100644 index 0000000000000000000000000000000000000000..d3909d43d24e3c4fddb5b5c5e172beda44bf573a GIT binary patch literal 109024 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 literal 0 HcmV?d00001 diff --git a/webpages/vm-operator/VM-Operator-GUI-preview.png b/webpages/vm-operator/VM-Operator-GUI-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..b5293d710d1b95511c37a0f9b25ab9148f6a40af GIT binary patch literal 40113 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 literal 0 HcmV?d00001 diff --git a/webpages/vm-operator/VM-Operator-GUI-view.png b/webpages/vm-operator/VM-Operator-GUI-view.png new file mode 100644 index 0000000000000000000000000000000000000000..0463cc58f2e4739353d8fa5505920254691db7ba GIT binary patch literal 42919 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%0D{rnnqehKW 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 + + + + + + + + + image/svg+xml + + + + + + + + + VM + + + + + + + + + + + + + + + + + diff --git a/webpages/vm-operator/VM-Operator.svg b/webpages/vm-operator/VM-Operator.svg new file mode 100644 index 0000000..c8616d5 --- /dev/null +++ b/webpages/vm-operator/VM-Operator.svg @@ -0,0 +1,184 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webpages/vm-operator/VmViewer-preview.png b/webpages/vm-operator/VmViewer-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..d13387f723813849a9bbd485f17f3b9d4abbf7d7 GIT binary patch literal 13757 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 + + + + + + + + + image/svg+xml + + + + + + + M + L + + diff --git a/webpages/vm-operator/index.md b/webpages/vm-operator/index.md new file mode 100644 index 0000000..04134d5 --- /dev/null +++ b/webpages/vm-operator/index.md @@ -0,0 +1,60 @@ +--- +title: VM-Operator by mnlipp +description: A Kubernetes operator for running virtual machines (notably Qemu VMs) in pods on Kubernetes +layout: vm-operator +--- + +# Welcome to VM-Operator + +The goal of this project is to provide the means for running Qemu +based VMs in Kubernetes pods. + +The image used for the VM pods combines Qemu and a control program +for starting and managing the Qemu process. This application is called +"[the runner](runner.md)". + +While you can deploy a runner manually (or with the help of some +helm templates), the preferred way is to deploy "[the manager](manager.md)" +application which acts as a Kubernetes operator for runners +and thus the VMs. + +If you just want to try out things, you can skip the remainder of this +page and proceed to "[the manager](manager.md)". + +## Motivation +The project was triggered by a remark in the discussion about RedHat +[dropping SPICE support](https://bugzilla.redhat.com/show_bug.cgi?id=2030592) +from the RHEL packages. Which means that you have to run Qemu in a +container on RHEL and derivatives if you want to continue using Spice. +So KubeVirt comes to mind. But +[one comment](https://bugzilla.redhat.com/show_bug.cgi?id=2030592#c4) +mentioned that the [KubeVirt](https://kubevirt.io/) project isn't +interested in supporting SPICE either. + +Time to have a look at alternatives. Libvirt has become a common +tool to configure and run Qemu. But some of its functionality, notably +the management of storage for the VMs and networking is already provided +by Kubernetes. Therefore this project takes a fresh approach of +running Qemu in a pod using a simple, lightweight manager called "runner". +Providing resources to the VM is left to Kubernetes mechanisms as +much as possible. + +## VMs and Pods + +VMs are not the typical workload managed by Kubernetes. You can neither +have replicas nor can the containers simply be restarted without a major +impact on the "application". So there are many features for managing +pods that we cannot make use of. Qemu in its container can only be +deployed as a pod or using a stateful set with replica 1, which is rather +close to simply deploying the pod (you get the restart and some PVC +management "for free"). + +A second look, however, reveals that Kubernetes has more to offer. +* It has a well defined API for managing resources. +* It provides access to different kinds of managed storage for the VMs. +* Its managing features *are* useful for running the component that +manages the pods with the VMs. + +And if you use Kubernetes anyway, well then the VMs within Kubernetes +provide you with a unified view of all (or most of) your workloads, +which simplifies the maintenance of your platform. diff --git a/webpages/vm-operator/manager.md b/webpages/vm-operator/manager.md new file mode 100644 index 0000000..4613201 --- /dev/null +++ b/webpages/vm-operator/manager.md @@ -0,0 +1,150 @@ +--- +title: VM-Operator Manager +layout: vm-operator +--- + +# The Manager + +The Manager is the program that provides the controller from the +[operator pattern](https://github.com/cncf/tag-app-delivery/blob/eece8f7307f2970f46f100f51932db106db46968/operator-wg/whitepaper/Operator-WhitePaper_v1-0.md#operator-components-in-kubernetes) +together with a Web-GUI. It should be run in a container in the cluster. + +## Installation + +A manager instance manages the VMs in its own namespace. The only +common (and therefore cluster scoped) resource used by all instances +is the CRD. It is available +[here](https://github.com/mnlipp/VM-Operator/raw/main/deploy/crds/vms-crd.yaml) +and must be created first. + +```sh +kubectl apply -f https://github.com/mnlipp/VM-Operator/raw/main/deploy/crds/vms-crd.yaml +``` + +The example above uses the CRD from the main branch. This is okay if +you apply it once. If you want to preserve the link for automatic +upgrades, you should use a link that points to one of the release branches. + +The next step is to create a namespace for the manager and the VMs, e.g. +`vmop-demo`. + +```sh +kubectl create namespace vmop-demo +``` + +Finally you have to create an account, the role, the binding etc. The +default files for creating these resources using the default namespace +can be found in the +[deploy](https://github.com/mnlipp/VM-Operator/tree/main/deploy) +directory. I recommend to use +[kustomize](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/) to create your own configuration. + +## Initial Configuration + +Use one of the `kustomize.yaml` files from the +[example](https://github.com/mnlipp/VM-Operator/tree/main/example) directory +as a starting point. The directory contains two examples. Here's the file +from subdirectory `local-path`: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +# Again, I recommend to use the deploy directory from a +# release branch for anything but test environments. +- https://github.com/mnlipp/VM-Operator/deploy + +namespace: vmop-demo + +patches: +- patch: |- + kind: PersistentVolumeClaim + apiVersion: v1 + metadata: + name: vmop-image-repository + spec: + # Default is ReadOnlyMany + accessModes: + - ReadWriteOnce + resources: + requests: + # Default is 100Gi + storage: 10Gi + # Default is to use the default storage class + storageClassName: local-path + +- patch: |- + kind: ConfigMap + apiVersion: v1 + metadata: + name: vm-operator + data: + config.yaml: | + "/Manager": + # "/GuiHttpServer": + # See section about the GUI + "/Controller": + "/Reconciler": + runnerDataPvc: + # Default is to use the default storage class + storageClassName: local-path +``` + +The sample file adds a namespace (`vmop-demo`) to all resource +definitions and patches the PVC `vmop-image-repository`. This is a volume +that is mounted into all pods that run a VM. The volume is intended +to be used as a common repository for CDROM images. The PVC must exist +and it must be bound before any pods can run. + +The second patch affects the small volume that is created for each +runner and contains the VM's configuration data such as the EFI vars. +The manager's default configuration causes the PVC for this volume +to be created with no storage class (which causes the default storage +class to be used). The patch provides a new configuration file for +the manager that makes the reconciler use local-path as storage +class for this PVC. Details about the manager configuration can be +found in the next section. + +Note that you need none of the patches if you are fine with using your +cluster's default storage class and this class supports ReadOnlyMany as +access mode. + +Check that the pod with the manager is running: + +```sh +kubectl -n vmop-demo get pods -l app.kubernetes.io/name=vm-operator +``` + +Proceed to the description of [the controller](controller.html) +for creating your first VM. + +## Configuration Details + +The [config map](https://github.com/mnlipp/VM-Operator/blob/main/deploy/vmop-config-map.yaml) +for the manager may provide a configuration file (`config.yaml`) and +a file with logging properties (`logging.properties`). Both files are mounted +into the container that runs the manager and are evaluated by the manager +on startup. If no files are provided, the manager uses built-in defaults. + +The configuration file for the Manager follows the conventions of +the [JGrapes](https://jgrapes.org/) component framework. +The keys that start with a slash select the component within the +application's component hierarchy. The mapping associated with the +selected component configures this component's properties. + +The available configuration options for the components can be found +in their respective JavaDocs (e.g. +[here](latest-release/javadoc/org/jdrupes/vmoperator/manager/Reconciler.html) +for the Reconciler). + +## Development Configuration + +The [dev-example](https://github.com/mnlipp/VM-Operator/tree/main/dev-example) +directory contains a `kustomize.yaml` that uses the development namespace +`vmop-dev` and creates a deployment for the manager with 0 replicas. + +This environment can be used for running the manager in the IDE. As the +namespace to manage cannot be detected from the environment, you must use + `-c ../dev-example/config.yaml` as argument when starting the manager. This +configures it to use the namespace `vmop-dev`. diff --git a/webpages/vm-operator/runner.md b/webpages/vm-operator/runner.md new file mode 100644 index 0000000..d580530 --- /dev/null +++ b/webpages/vm-operator/runner.md @@ -0,0 +1,108 @@ +--- +title: VM-Operator Runner +layout: vm-operator +--- + +# The Runner + +For most use cases, Qemu needs to be started and controlled by another +program that manages the Qemu process. This program is called the +runner in this context. + +The most prominent reason for this second program is that it allows +a VM to be shutdown cleanly in response to a TERM signal. Qemu handles +the TERM signal by flushing all buffers and stopping, leaving the disks in +a [crash consistent state](https://gitlab.com/qemu-project/qemu/-/issues/148). +For a graceful shutdown, a parent process must handle the TERM signal, send +the `system_powerdown` command to the qemu process and wait for its completion. + +Another reason for having the runner is that another process needs to be started +before qemu if the VM is supposed to include a TPM (software TPM). + +Finally, we want some kind of higher level interface for applying runtime +changes to the VM such as changing the CD or configuring the number of +CPUs and the memory. + +The runner takes care of all these issues. Although it is intended to +run in a container (which runs in a Kubernetes pod) it does not require +a container. You can start and use it as an ordinary program on any +system, provided that you have the required commands (qemu, swtpm) +installed. + +## Stand-alone Configuration + +Upon startup, the runner reads its main configuration file +which defaults to `/etc/opt/vmrunner/config.yaml` and may be changed +using the `-c` (or `--config`) command line option. + +A sample configuration file with annotated options can be found +[here](https://github.com/mnlipp/VM-Operator/blob/main/org.jdrupes.vmoperator.runner.qemu/config-sample.yaml). +As the runner implementation uses the +[JGrapes](https://jgrapes.org/) framework, the file +follows the framework's +[conventions](https://jgrapes.org/latest-release/javadoc/org/jgrapes/util/YamlConfigurationStore.html). The top level "`/Runner`" selects +the component to be configured. Nested within is the information +to be applied to the component. + +The main entries in the configuration file are the "template" and +the "vm" information. The runner processes the +[freemarker template](https://freemarker.apache.org/), using the +"vm" information to derive the qemu command. The idea is that +the "vm" section provides high level information such as the boot +mode, the number of CPUs, the RAM size and the disks. The template +defines a particular VM type, i.e. it contains the "nasty details" +that do not need to be modified for some given set of VM instances. + +The templates provided with the runner can be found +[here](https://github.com/mnlipp/VM-Operator/tree/main/org.jdrupes.vmoperator.runner.qemu/templates). When details +of the VM configuration need modification, a new VM type +(i.e. a new template) has to be defined. Authoring a new +template requires some knowledge about the +[qemu invocation](https://www.qemu.org/docs/master/system/invocation.html). +Despite many "warnings" that you find in the web, configuring the +invocation arguments of qemu is only a bit (but not much) more +challenging than editing libvirt's XML. + +## Running in a Pod + +The real purpose of the runner is to run a VM on Kubernetes in a pod. +When running in a Kubernetes pod, `/etc/opt/vmrunner/config.yaml` should be +provided by a +[ConfigMap](https://kubernetes.io/docs/concepts/configuration/configmap/). + +If additional templates are required, some ReadOnlyMany PV should +be mounted in `/opt/vmrunner/templates`. The PV should contain copies +of the standard templates as well as the additional templates. Of course, +a ConfigMap can be used for this purpose again. + +Networking options are rather limited. The assumption is that in general +the VM wants full network connectivity. To achieve this, the pod must +run with host networking and the host's networking must provide a +bridge that the VM can attach to. The only currently supported +alternative is the less performant +"[user networking](https://wiki.qemu.org/Documentation/Networking#User_Networking_(SLIRP))", +which may be used in a stand-alone development configuration. + +## Runtime changes + +The runner supports adaption to changes of the RAM size (using the +balloon device) and to changes of the number of CPUs. Note that +in order to get new CPUs online on Linux guests, you need a +[udev rule](https://docs.kernel.org/core-api/cpu_hotplug.html#user-space-notification) which is not installed by default[^simplest]. + +The runner also changes the images loaded in CDROM drives. If the +drive is locked, i.e. if it doesn't respond to the "open tray" command +the change will be suspended until the VM opens the tray. + +Finally, `powerdownTimeout` can be changed while the qemu process runs. + +[^simplest]: The simplest form of the rule is probably: + ``` + ACTION=="add", SUBSYSTEM=="cpu", ATTR{online}="1" + ``` + +## Testing with Helm + +There is a +[Helm Chart](https://github.com/mnlipp/VM-Operator/tree/main/org.jdrupes.vmoperator.runner.qemu/helm-test) +for testing the runner. diff --git a/webpages/vm-operator/upgrading.md b/webpages/vm-operator/upgrading.md new file mode 100644 index 0000000..41d04ad --- /dev/null +++ b/webpages/vm-operator/upgrading.md @@ -0,0 +1,29 @@ +--- +title: Upgrading +layout: vm-operator +--- + +# Upgrading + +## To version 3.0.0 + +All configuration files are backward compatible to version 2.3.0. +Note that in order to make use of the new viewer component, +[permissions](https://mnlipp.github.io/VM-Operator/user-gui.html#control-access-to-vms) +must be configured in the CR definition. Also note that +[display secrets](https://mnlipp.github.io/VM-Operator/user-gui.html#securing-access) +are automatically created unless explicitly disabled. + +## To version 2.3.0 + +Starting with version 2.3.0, the web GUI uses a login conlet that +supports OIDC providers. This effects the configuration of the +web GUI components. + +## To version 2.2.0 + +Version 2.2.0 sets the stateful set's `.spec.updateStrategy.type` to +"OnDelete". This fails for no apparent reason if a definition of +the stateful set with the default value "RollingUpdate" already exists. +In order to fix this, either the stateful set or the complete VM definition +must be deleted and the manager must be restarted. diff --git a/webpages/vm-operator/user-gui.md b/webpages/vm-operator/user-gui.md new file mode 100644 index 0000000..394c28f --- /dev/null +++ b/webpages/vm-operator/user-gui.md @@ -0,0 +1,143 @@ +--- +title: VM-Operator Web-GUI for Users +layout: vm-operator +--- + +# User view + +*Since 3.0.0* + +The idea of the user view is to provide an intuitive widget that +allows the users to access their own VMs and to optionally start +and stop them. + +![VM-Viewer](VmViewer-preview.png) + +The configuration options resulting from this seemingly simple +requirement are unexpectedly complex. + +## Control access to VMs + +First of all, we have to define which VMs a user can access. This +is done using the optional property `spec.permissions` of the +VM definition (CRD). + +```yaml +spec: + permissions: + - role: admin + may: + - "*" + - user: test + may: + - start + - stop + - accessConsole +``` + +Permissions can be granted to individual users or to roles. There +is a permission for each possible action. "*" grants them all. + +## Simple usage vs. expert usage + +Next, there are two ways to create the VM widgets (preview conlets +in the framework's terms). They can be created on demand or +automatically for each VM that a logged in user has permission to +access. The former is the preferred way for an administrator who +has access to all VMs and needs to open a particular VM's console +for trouble shooting only. The latter is the preferred way +for a regular user who has access to a limited number of VMs. +In this case, creating the widgets automatically has the additional +benefit that regular users don't need to know how to create and +configure the widgets using the menu and the properties dialog. + +Automatic synchronization of widgets and accessible VMs is controlled +by the property `syncPreviewsFor` of the VM viewer. It's an array with +objects that either specify a role or a user. + +```yaml +"/Manager": + # This configures the GUI + "/GuiHttpServer": + "/ConsoleWeblet": + "/WebConsole": + "/ComponentCollector": + "/VmViewer": + syncPreviewsFor: + - role: user + - user: test + displayResource: + preferredIpVersion: ipv4 +``` + +## Console access + +Access to the VM's console is implemented by generating a +[connection file](https://manpages.debian.org/testing/virt-viewer/remote-viewer.1.en.html#CONNECTION_FILE) for virt-viewer when the user clicks on +the console icon. If automatic open is enabled for this kind of +files in the browser, the console opens without further user action. + +The file contains all required and optional information to start the +remote viewer. + + * The "host" is by default the IP address of the node that the + VM's pod is running on (remember that the runner uses host + networking). + * The "port" is simply taken from the VM definition. + +In more complex scenarios, an administrator may have set up a load +balancer that hides the worker node's IP addresses or the worker +nodes use an internal network and can only be accessed through a +proxy. For both cases, the values to include in the connection file +can be specified as properties of `spec.vm.display.spice` in the +VM definition. + +```yaml +spec: + vm: + display: + spice: + port: 5930 + server: 192.168.19.32 + proxyUrl: http://lgpe-spice.some.host:1234 + generateSecret: true +``` + +The value of `server` is used as value for key "host" in the +connection file, thus overriding the default value. The +value of `proxyUrl` is used as value for key "proxy". + +## Securing access + +As described [previously](./controller.html#display-secretpassword), +access to a VM's display can be secured with a password. If a secret +with a password exists for a VM, the password is +included in the connection file. + +While this approach is very convenient for the user, it is not +secure, because this leaves the password as plain text in a file on +the user's computer (the downloaded connection file). To work around +this, the display secret is updated with a random password with +limited validity, unless the display secret defines a `password-expiry` +in the future or with value "never" or doesn't define a +`password-expiry` at all. + +The automatically generated password is the base64 encoded value +of 16 (strong) random bytes (128 random bits). It is valid for +10 seconds only. This may be challenging on a slower computer +or if users may not enable automatic open for connection files +in the browser. The validity can therefore be adjusted in the +configuration. + +```yaml +"/Manager": + "/Controller": + "/DisplaySecretMonitor": + # Validity of generated password in seconds + passwordValidity: 10 +``` + +Taking into account that the controller generates a display +secret automatically by default, this approach to securing +console access should be sufficient in all cases. (Any feedback +if something has been missed is appreciated.) diff --git a/webpages/vm-operator/webgui.md b/webpages/vm-operator/webgui.md new file mode 100644 index 0000000..38b9faa --- /dev/null +++ b/webpages/vm-operator/webgui.md @@ -0,0 +1,117 @@ +--- +title: VM-Operator Web-GUI +layout: vm-operator +--- + +# The Web-GUI + +The manager component provides a GUI via a web server. The web GUI is +implemented using components from the +[JGrapes WebConsole](https://jgrapes.org/WebConsole.html) +project. Configuration of the GUI therefore follows the conventions +of that framework. + +The structure of the configuration information should be easy to +understand from the examples provided. In general, configuration values +are applied to the individual components that make up an application. +The hierarchy of the components is reflected in the configuration +information because components are "addressed" by their position in +that hierarchy. (See +[the package description](latest-release/javadoc/org/jdrupes/vmoperator/manager/package-summary.html) +for information about the complete component structure.) + +## Network access + +By default, the service is made available at port 8080 of the manager +pod. Of course, a kubernetes service and an ingress configuration must +be added as required by the environment. (See the +[definition](https://github.com/mnlipp/VM-Operator/blob/main/deploy/vmop-service.yaml) +from the +[sample deployment](https://github.com/mnlipp/VM-Operator/tree/main/deploy)). + +## User Access + +Access to the web GUI is controlled by the login conlet. The framework +does not include sophisticated components for user management. Rather, +it assumes that an OIDC provider is responsible for user authentication +and role management. + +```yaml +"/Manager": + # "/GuiSocketServer": + # port: 8080 + "/GuiHttpServer": + # This configures the GUI + "/ConsoleWeblet": + "/WebConsole": + "/LoginConlet": + # Starting with version 2.3.0 the preferred approach is to + # configure an OIDC provider for user management and + # authorization. See the text for details. + oidcProviders: {} + + # Support for "local" users is provided as a fallback mechanism. + # Note that up to Version 2.2.x "users" was an object with user names + # as its properties. Starting with 2.3.0 it is a list as shown. + users: + - name: admin + fullName: Administrator + password: "Generate hash with bcrypt" + - name: test + fullName: Test Account + password: "Generate hash with bcrypt" + + # Required for using OIDC, see the text for details. + "/OidcClient": + redirectUri: https://my.server.here/oauth/callback" + + # May be used for assigning roles to both local users and users from + # the OIDC provider. Not needed if roles are managed by the OIDC provider. + "/RoleConfigurator": + rolesByUser: + # User admin has role admin + admin: + - admin + # Non-privileged users are users + test: + - user + # All users have role other + "*": + - other + replace: false + + # Manages the permissions for the roles. + "/RoleConletFilter": + conletTypesByRole: + # Admins can use all conlets + admin: + - "*" + # Users can use the viewer conlet + user: + - org.jdrupes.vmoperator.vmviewer.VmViewer + # Others cannot use any conlet (except login conlet to log out) + other: + # Up to version 2.2.x + # - org.jgrapes.webconlet.locallogin.LoginConlet + # Starting with version 2.3.0 + - org.jgrapes.webconlet.oidclogin.LoginConlet +``` + +How local users can be configured should be obvious from the example. +The configuration of OIDC providers for user authentication (and +optionally for role assignment) is explained in the documentation of the +[login conlet](https://jgrapes.org/javadoc-webconsole/org/jgrapes/webconlet/oidclogin/LoginConlet.html). +Details about the `RoleConfigurator` and `RoleConletFilter` can also be found +in the documentation of the +[JGrapes WebConsole](https://jgrapes.org/WebConsole.html) +project. + +The configuration above allows all users with role "admin" to use all +GUI components and users with role "user" to only use the viewer conlet, +i.e. the [User view](user-gui.html). The fallback role "other" allows +all users to use the login conlet to log out. + +## Views + +The configuration of the components that provide the manager and +users views is explained in the respective sections. From 6a2d71164319de722c2ced7d7218dcf7406d95af Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 20 Jun 2024 22:42:26 +0200 Subject: [PATCH 151/462] New publishing style. --- .github/workflows/gradle.yml | 2 +- ....jdrupes.vmoperator.java-doc-conventions.gradle | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index afb5aab..f47366a 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -28,4 +28,4 @@ jobs: java-version: '21' distribution: 'temurin' - name: Build with Gradle - run: ./gradlew -Prepo.access.token=${{ secrets.REPO_ACCESS_TOKEN }} stage + run: ./gradlew -Pwebsite.push.token=${{ secrets.WEBSITE_PUSH_TOKEN }} stage diff --git a/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle b/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle index 8e8413e..5eed550 100644 --- a/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle +++ b/buildSrc/src/org.jdrupes.vmoperator.java-doc-conventions.gradle @@ -116,20 +116,24 @@ task testJavadoc(type: Javadoc) { // Prepare github authentication for plugins if (System.properties['org.ajoberstar.grgit.auth.username'] == null) { System.setProperty('org.ajoberstar.grgit.auth.username', - project.rootProject.properties['repo.access.token'] ?: "nouser") + project.rootProject.properties['website.push.token'] ?: "nouser") } gitPublish { - repoUri = 'https://github.com/mnlipp/VM-Operator.git' - branch = 'gh-pages' + repoUri = 'https://github.com/mnlipp/jdrupes.org.git' + branch = 'main' contents { + from("${rootProject.projectDir}/webpages") { + include '_layouts/vm-operator.html' + include 'vm-operator/**' + } from("${rootProject.buildDir}/javadoc") { - into 'javadoc' + into 'vm-operator/javadoc' } if (!findProject(':org.jdrupes.vmoperator.runner.qemu').isSnapshot && !findProject(':org.jdrupes.vmoperator.manager').isSnapshot) { from("${rootProject.buildDir}/javadoc") { - into 'latest-release/javadoc' + into 'vm-operator/latest-release/javadoc' } } } From 9d5f3cf702aaca143425f068ecd42400266b94bd Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 20 Jun 2024 23:11:01 +0200 Subject: [PATCH 152/462] Update link. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 176437a..52a2fa8 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,5 @@ The goal of this project is to provide the means for running Qemu based VMs in Kubernetes pods. -See the [project's home page](https://mnlipp.github.io/VM-Operator/) +See the [project's home page](https://jdrupes.org/vm-operator/) for details. From 128ffbc2ad3fab16e5702f0d86a4a95725baeaa6 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 22 Jun 2024 11:52:32 +0200 Subject: [PATCH 153/462] Update. --- misc/javadoc.bottom.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/misc/javadoc.bottom.txt b/misc/javadoc.bottom.txt index bf7dd56..a630b6f 100644 --- a/misc/javadoc.bottom.txt +++ b/misc/javadoc.bottom.txt @@ -9,15 +9,14 @@ var _paq = _paq || []; /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ _paq.push(["setDocumentTitle", document.domain + "/" + document.title]); - _paq.push(["setCookieDomain", "*.mnlipp.github.io"]); - _paq.push(["setDomains", ["*.mnlipp.github.io"]]); + _paq.push(["setCookieDomain", "*.jdrupes.org"]); _paq.push(['disableCookies']); _paq.push(['trackPageView']); _paq.push(['enableLinkTracking']); (function() { - var u="//piwik.mnl.de/"; + var u="//jdrupes.org/"; _paq.push(['setTrackerUrl', u+'piwik.php']); - _paq.push(['setSiteId', '14']); + _paq.push(['setSiteId', '15']); var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s); })(); From 7dea95660cadaa45ca3850a42daf69649ced947c Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 22 Jun 2024 14:32:11 +0200 Subject: [PATCH 154/462] Update. --- misc/javadoc.bottom.txt | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/misc/javadoc.bottom.txt b/misc/javadoc.bottom.txt index a630b6f..42d0ccd 100644 --- a/misc/javadoc.bottom.txt +++ b/misc/javadoc.bottom.txt @@ -4,25 +4,30 @@ TermsPrivacy

+
+ +

+ +

View GitHub Project

+ +

+ +

Overview

+

The Runner

+

The Manager

+ +

The Web-GUI

+
+
+ + {% if page.tocTitle %} +

{{ page.tocTitle }}

+ {% include toc.html html=content %} + {% endif %} + + {{ content }} +
+ +

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 bnSERoeV@9DgSt*4_MNhCCEqz|UQYgh!ZM0I diff --git a/misc/DejaVuSansMono-Bold.woff2 b/misc/DejaVuSansMono-Bold.woff2 deleted file mode 100644 index f2b469a6c224ab9614577089d40917cfb8b34ac3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 143796 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%0Dx8ML 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

\ No newline at end of file From 63c03dae81eb4975a3c3ece02b72d653324d35ff Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 22 Jun 2024 14:40:14 +0200 Subject: [PATCH 155/462] Publish javadoc with JDK-21. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1a11881..d0ebc71 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ task stage { tc -> tc.findByName("build") }.flatten() } - if (JavaVersion.current() == JavaVersion.VERSION_17) { + if (JavaVersion.current() == JavaVersion.VERSION_21) { // Publish JavaDoc dependsOn gitPublishPush } From a50809211d8a09868c3d9660d3e8224649afa130 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 22 Jun 2024 14:46:54 +0200 Subject: [PATCH 156/462] Fix name. --- misc/javadoc.bottom.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/javadoc.bottom.txt b/misc/javadoc.bottom.txt index 42d0ccd..abf54f3 100644 --- a/misc/javadoc.bottom.txt +++ b/misc/javadoc.bottom.txt @@ -4,7 +4,7 @@ TermsPrivacy

From 86837bc81b4dbf371884da16e97f399c0f3cd24c Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 22 Jun 2024 15:11:01 +0200 Subject: [PATCH 157/462] Fix token. --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a9b5d98..161b7c8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,4 +37,4 @@ jobs: java-version: '21' distribution: 'temurin' - name: Push with Gradle - run: ./gradlew -Prepo.access.token=${{ secrets.REPO_ACCESS_TOKEN }} -Pdocker.registry=ghcr.io/${{ github.actor }} stage pushImages + run: ./gradlew -Pwebsite.push.token=${{ secrets.WEBSITE_PUSH_TOKEN }} -Pdocker.registry=ghcr.io/${{ github.actor }} stage pushImages From 28691b64439588de1201f460760b1428e2c19399 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 22 Jun 2024 16:06:31 +0200 Subject: [PATCH 158/462] Make dependencies branch specific. --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a8673aa..740fd8f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,7 +12,7 @@ stages: extends: .any-job image: registry.mnl.de/org/jgrapes/jdk21-builder:v2 cache: - - key: dependencies + - key: dependencies-CI_COMMIT_BRANCH policy: pull-push paths: - .gradle From e830815400fbff6ab0e6558eaaf813b9ff41fd0f Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 22 Jun 2024 23:03:28 +0200 Subject: [PATCH 159/462] Add dependency. --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 740fd8f..62a51fb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -37,6 +37,8 @@ build-jars: publish-images: stage: publish extends: .gradle-job + dependencies: + - build-jars script: - ./gradlew -Pdocker.registry=$CI_REGISTRY_IMAGE pushImage From 12ca211fdba91a625cbc69402672864cef8cb1e2 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 23 Jun 2024 11:25:19 +0200 Subject: [PATCH 160/462] Fix env reference. --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 62a51fb..13b12fd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,7 +12,7 @@ stages: extends: .any-job image: registry.mnl.de/org/jgrapes/jdk21-builder:v2 cache: - - key: dependencies-CI_COMMIT_BRANCH + - key: dependencies-${CI_COMMIT_BRANCH} policy: pull-push paths: - .gradle From 7df0cc386ccdc0960e55ca5782bfbe98b722685c Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 23 Jun 2024 12:52:18 +0200 Subject: [PATCH 161/462] Fix image building. --- .github/workflows/release.yml | 2 +- .gitlab-ci.yml | 2 +- org.jdrupes.vmoperator.manager/build.gradle | 35 +++++---- .../build.gradle | 75 ++++++++++--------- spice-squid/build.gradle | 56 ++++++-------- 5 files changed, 86 insertions(+), 84 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 161b7c8..beab0c4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,4 +37,4 @@ jobs: java-version: '21' distribution: 'temurin' - name: Push with Gradle - run: ./gradlew -Pwebsite.push.token=${{ secrets.WEBSITE_PUSH_TOKEN }} -Pdocker.registry=ghcr.io/${{ github.actor }} stage pushImages + run: ./gradlew -Pwebsite.push.token=${{ secrets.WEBSITE_PUSH_TOKEN }} -Pdocker.registry=ghcr.io/${{ github.actor }} stage publishImage diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 13b12fd..04acdec 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -40,7 +40,7 @@ publish-images: dependencies: - build-jars script: - - ./gradlew -Pdocker.registry=$CI_REGISTRY_IMAGE pushImage + - ./gradlew -Pdocker.registry=$CI_REGISTRY_IMAGE publishImage .pages-job: extends: .any-job diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index a956a00..45d9494 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -46,6 +46,7 @@ application { } project.ext.gitBranch = grgit.branch.current.name.replace('/', '-') +def registry = "${project.rootProject.properties['docker.registry']}" task buildImage(type: Exec) { dependsOn installDist @@ -61,20 +62,23 @@ task pushImage(type: Exec) { // Don't push without testing first dependsOn test - def registry = "${project.rootProject.properties['docker.registry']}" commandLine 'podman', 'push', '--tls-verify=false', \ - "localhost/${project.name}:${project.gitBranch}", \ + "${project.name}:${project.gitBranch}", \ "${registry}/${project.name}:${project.gitBranch}" - - if (!project.version.contains("SNAPSHOT")) { - commandLine 'podman', 'tag', \ - "${registry}/${project.name}:${project.gitBranch}",\ - "${registry}/${project.name}:${project.version}" - } +} + +task tagWithVersion(type: Exec) { + dependsOn pushImage + + enabled = !project.version.contains("SNAPSHOT") + + commandLine 'podman', 'push', \ + "${project.name}:${project.gitBranch}",\ + "${registry}/${project.name}:${project.version}" } task tagAsLatest(type: Exec) { - dependsOn pushImage + dependsOn tagWithVersion enabled = !project.version.contains("SNAPSHOT") && !project.version.contains("alpha") \ @@ -83,17 +87,22 @@ task tagAsLatest(type: Exec) { && project.rootProject.properties['docker.registry'] \ == project.rootProject.properties['docker.testRegistry'] - def registry = "${project.rootProject.properties['docker.registry']}" - commandLine 'podman', 'tag', \ - "${registry}/${project.name}:${project.version}",\ + commandLine 'podman', 'push', \ + "${project.name}:${project.gitBranch}",\ "${registry}/${project.name}:latest" } +task publishImage { + dependsOn pushImage + dependsOn tagWithVersion + dependsOn tagAsLatest +} + task pushForTest(type: Exec) { dependsOn buildImage commandLine 'podman', 'push', '--tls-verify=false', \ - "localhost/${project.name}:${project.gitBranch}", \ + "${project.name}:${project.gitBranch}", \ "${project.rootProject.properties['docker.testRegistry']}" \ + "/${project.name}:test" } diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle index 00bf7ea..8cea1ae 100644 --- a/org.jdrupes.vmoperator.runner.qemu/build.gradle +++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle @@ -32,8 +32,9 @@ application { } project.ext.gitBranch = grgit.branch.current.name.replace('/', '-') +def registry = "${project.rootProject.properties['docker.registry']}" -task buildArchImage(type: Exec) { +task buildImageArch(type: Exec) { dependsOn installDist inputs.files 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch' @@ -42,23 +43,26 @@ task buildArchImage(type: Exec) { '-f', 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch', '.' } -task pushArchImage(type: Exec) { - dependsOn buildArchImage +task pushImageArch(type: Exec) { + dependsOn buildImageArch - def registry = "${project.rootProject.properties['docker.registry']}" commandLine 'podman', 'push', '--tls-verify=false', \ - "localhost/${project.name}-arch:${project.gitBranch}", \ + "${project.name}-arch:${project.gitBranch}", \ "${registry}/${project.name}-arch:${project.gitBranch}" - - if (!project.version.contains("SNAPSHOT")) { - commandLine 'podman', 'tag', \ - "${registry}/${project.name}-arch:${project.gitBranch}",\ - "${registry}/${project.name}-arch:${project.version}" - } +} + +task tagWithVersionArch(type: Exec) { + dependsOn pushImageArch + + enabled = !project.version.contains("SNAPSHOT") + + commandLine 'podman', 'push', \ + "${project.name}-arch:${project.gitBranch}",\ + "${registry}/${project.name}-arch:${project.version}" } task tagAsLatestArch(type: Exec) { - dependsOn pushArchImage + dependsOn tagWithVersionArch enabled = !project.version.contains("SNAPSHOT") && !project.version.contains("alpha") \ @@ -67,13 +71,12 @@ task tagAsLatestArch(type: Exec) { && project.rootProject.properties['docker.registry'] \ == project.rootProject.properties['docker.testRegistry'] - def registry = "${project.rootProject.properties['docker.registry']}" - commandLine 'podman', 'tag', \ - "${registry}/${project.name}-arch:${project.version}",\ + commandLine 'podman', 'push', \ + "${project.name}-arch:${project.gitBranch}",\ "${registry}/${project.name}-arch:latest" } -task buildAlpineImage(type: Exec) { +task buildImageAlpine(type: Exec) { dependsOn installDist inputs.files 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine' @@ -82,23 +85,26 @@ task buildAlpineImage(type: Exec) { '-f', 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine', '.' } -task pushAlpineImage(type: Exec) { - dependsOn buildAlpineImage +task pushImageAlpine(type: Exec) { + dependsOn buildImageAlpine - def registry = "${project.rootProject.properties['docker.registry']}" commandLine 'podman', 'push', '--tls-verify=false', \ "localhost/${project.name}-alpine:${project.gitBranch}", \ "${registry}/${project.name}-alpine:${project.gitBranch}" - - if (!project.version.contains("SNAPSHOT")) { - commandLine 'podman', 'tag', \ - "${registry}/${project.name}-alpine:${project.gitBranch}",\ - "${registry}/${project.name}-alpine:${project.version}" - } +} + +task tagWithVersionAlpine(type: Exec) { + dependsOn pushImageAlpine + + enabled = !project.version.contains("SNAPSHOT") + + commandLine 'podman', 'push', \ + "${project.name}-alpine:${project.gitBranch}",\ + "${registry}/${project.name}-alpine:${project.version}" } task tagAsLatestAlpine(type: Exec) { - dependsOn pushAlpineImage + dependsOn tagWithVersionAlpine enabled = !project.version.contains("SNAPSHOT") && !project.version.contains("alpha") \ @@ -107,19 +113,16 @@ task tagAsLatestAlpine(type: Exec) { && project.rootProject.properties['docker.registry'] \ == project.rootProject.properties['docker.testRegistry'] - def registry = "${project.rootProject.properties['docker.registry']}" - commandLine 'podman', 'tag', \ - "${registry}/${project.name}-alpine:${project.version}",\ + commandLine 'podman', 'push', \ + "${project.name}-alpine:${project.gitBranch}",\ "${registry}/${project.name}-alpine:latest" } -task pushImage { - dependsOn pushArchImage - dependsOn pushAlpineImage -} - -task tagAsLatest { +task publishImage { + dependsOn pushImageArch + dependsOn tagWithVersionArch dependsOn tagAsLatestArch + dependsOn pushImageAlpine + dependsOn tagWithVersionAlpine dependsOn tagAsLatestAlpine } - diff --git a/spice-squid/build.gradle b/spice-squid/build.gradle index 2cb7183..e7ccd00 100644 --- a/spice-squid/build.gradle +++ b/spice-squid/build.gradle @@ -5,44 +5,36 @@ plugins { dependencies { } +project.ext.gitBranch = grgit.branch.current.name.replace('/', '-') +def registry = "${project.rootProject.properties['docker.registry']}" + task buildImage(type: Exec) { inputs.files 'Containerfile' commandLine 'podman', 'build', '--pull', - '-t', "${project.name}:${project.version}",\ + '-t', "${project.name}:${project.gitBranch}",\ '-f', 'Containerfile', '.' } -task tagLatestImage(type: Exec) { - dependsOn buildImage - - enabled = !project.version.contains("SNAPSHOT") - && !project.version.contains("alpha") \ - && !project.version.contains("beta") \ - || project.rootProject.properties['docker.testRegistry'] \ - && project.rootProject.properties['docker.registry'] \ - == project.rootProject.properties['docker.testRegistry'] - - commandLine 'podman', 'tag', "${project.name}:${project.version}",\ - "${project.name}:latest" -} - -task buildLatestImage { - dependsOn buildImage - dependsOn tagLatestImage -} - task pushImage(type: Exec) { dependsOn buildImage commandLine 'podman', 'push', '--tls-verify=false', \ - "localhost/${project.name}:${project.version}", \ - "${project.rootProject.properties['docker.registry']}" \ - + "/${project.name}:${project.version}" + "${project.name}:${project.gitBranch}", \ + "${registry}/${project.name}:${project.gitBranch}" +} +task tagWithVersion(type: Exec) { + dependsOn pushImage + + enabled = !project.version.contains("SNAPSHOT") + + commandLine 'podman', 'push', \ + "${project.name}:${project.gitBranch}",\ + "${registry}/${project.name}:${project.version}" } -task pushLatestImage(type: Exec) { - dependsOn buildLatestImage +task tagAsLatest(type: Exec) { + dependsOn tagWithVersion enabled = !project.version.contains("SNAPSHOT") && !project.version.contains("alpha") \ @@ -51,18 +43,16 @@ task pushLatestImage(type: Exec) { && project.rootProject.properties['docker.registry'] \ == project.rootProject.properties['docker.testRegistry'] - commandLine 'podman', 'push', '--tls-verify=false', \ - "localhost/${project.name}:${project.version}", \ - "${project.rootProject.properties['docker.registry']}" \ - + "/${project.name}:latest" + commandLine 'podman', 'push', \ + "${project.name}:${project.gitBranch}",\ + "${registry}/${project.name}:latest" } -task pushImages { - // Don't push without testing first +task publishImage { dependsOn pushImage - dependsOn pushLatestImage + dependsOn tagWithVersion + dependsOn tagAsLatest } - test { enabled = project.hasProperty("k8s.testCluster") From 8b83a0cbc8bb5001ea3755f6dc3e5d5ed4886d01 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 23 Jun 2024 17:02:44 +0200 Subject: [PATCH 162/462] Make sure to switch to branch. --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 04acdec..84d99d7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,7 +24,7 @@ stages: - "*/build" before_script: - echo -n $CI_REGISTRY_PASSWORD | podman login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY - - git switch $CI_COMMIT_REF_NAME + - git switch $CI_COMMIT_BRANCH - git pull - git reset --hard $CI_COMMIT_SHA From e994fa154333af1d27f951e339d77ce2a6fcf690 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 23 Jun 2024 21:41:00 +0200 Subject: [PATCH 163/462] Another attempt to find the branch. --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 84d99d7..9a07e7c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,7 +24,7 @@ stages: - "*/build" before_script: - echo -n $CI_REGISTRY_PASSWORD | podman login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY - - git switch $CI_COMMIT_BRANCH + - git switch $(git branch -r --contains $CI_COMMIT_SHA | sed -e 's#[^/]*/##') - git pull - git reset --hard $CI_COMMIT_SHA From 8a434d8410e096d6efeee6b4dc083003224e14a7 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 24 Jun 2024 10:04:09 +0200 Subject: [PATCH 164/462] Use newest branch if we have multiple matches. --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9a07e7c..7dff899 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -24,7 +24,7 @@ stages: - "*/build" before_script: - echo -n $CI_REGISTRY_PASSWORD | podman login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY - - git switch $(git branch -r --contains $CI_COMMIT_SHA | sed -e 's#[^/]*/##') + - git switch $(git branch -r --sort="authordate" --contains $CI_COMMIT_SHA | head -1 | sed -e 's#[^/]*/##') - git pull - git reset --hard $CI_COMMIT_SHA From fc29786afe6d0234e08350597563604fa6fc3316 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Mon, 24 Jun 2024 11:18:35 +0200 Subject: [PATCH 165/462] Fix links. --- webpages/vm-operator/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webpages/vm-operator/index.md b/webpages/vm-operator/index.md index 04134d5..232a9ce 100644 --- a/webpages/vm-operator/index.md +++ b/webpages/vm-operator/index.md @@ -11,15 +11,15 @@ based VMs in Kubernetes pods. The image used for the VM pods combines Qemu and a control program for starting and managing the Qemu process. This application is called -"[the runner](runner.md)". +"[the runner](runner.html)". While you can deploy a runner manually (or with the help of some -helm templates), the preferred way is to deploy "[the manager](manager.md)" +helm templates), the preferred way is to deploy "[the manager](manager.html)" application which acts as a Kubernetes operator for runners and thus the VMs. If you just want to try out things, you can skip the remainder of this -page and proceed to "[the manager](manager.md)". +page and proceed to "[the manager](manager.html)". ## Motivation The project was triggered by a remark in the discussion about RedHat From 1fe7960d24cc1397fc8147d1317f3a70cb516247 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Wed, 26 Jun 2024 14:14:45 +0200 Subject: [PATCH 166/462] Update. --- webpages/vm-operator/favicon.svg | 196 +++++++++++++++++++++++-------- 1 file changed, 146 insertions(+), 50 deletions(-) diff --git a/webpages/vm-operator/favicon.svg b/webpages/vm-operator/favicon.svg index e216c44..c8616d5 100644 --- a/webpages/vm-operator/favicon.svg +++ b/webpages/vm-operator/favicon.svg @@ -2,23 +2,20 @@ + inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" + sodipodi:docname="VM-Operator.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + inkscape:window-y="32" + inkscape:window-maximized="1" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" /> @@ -51,7 +51,6 @@ image/svg+xml - @@ -59,30 +58,127 @@ inkscape:label="Ebene 1" inkscape:groupmode="layer" id="layer1" - transform="translate(-175.34341,-117.71255)"> - M - L + transform="translate(799.83239,410.74206)"> + + + + + + + + + + + + + + + + + + + + + + + + + + From 6852c575ae4a08f42da68f99f550f6726da73900 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Wed, 26 Jun 2024 21:43:28 +0200 Subject: [PATCH 167/462] Fix hover. --- .../org/jdrupes/vmoperator/vmviewer/browser/VmViewer-style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-style.scss b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-style.scss index 6d0654f..3ee432a 100644 --- a/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-style.scss +++ b/org.jdrupes.vmoperator.vmviewer/src/org/jdrupes/vmoperator/vmviewer/browser/VmViewer-style.scss @@ -72,6 +72,7 @@ position: absolute; animation: spin 2s linear infinite; z-index: 100; + pointer-events: none; } } From b74de67c6d6f61daa60d30cc4b4853288228bc10 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 27 Jun 2024 14:56:06 +0200 Subject: [PATCH 168/462] Update icon. --- webpages/vm-operator/VM-Operator.svg | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/webpages/vm-operator/VM-Operator.svg b/webpages/vm-operator/VM-Operator.svg index c8616d5..30c1ed2 100644 --- a/webpages/vm-operator/VM-Operator.svg +++ b/webpages/vm-operator/VM-Operator.svg @@ -7,7 +7,7 @@ viewBox="0 0 331.50461 323.22329" id="svg2" version="1.1" - inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" + inkscape:version="1.3.2 (091e20ef0f, 2023-11-25)" sodipodi:docname="VM-Operator.svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" @@ -25,9 +25,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="0.7" - inkscape:cx="245" - inkscape:cy="145.71429" + inkscape:zoom="1.4" + inkscape:cx="190.35714" + inkscape:cy="178.57143" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" @@ -171,12 +171,12 @@ cy="-109.05605" r="9.2055216" /> + transform="matrix(0.32800241,0,0,0.32572486,-723.34527,-233.01684)"> From e3da87d94fd5415f2c83f5ae0f3e334da6689487 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 27 Jun 2024 16:50:49 +0200 Subject: [PATCH 169/462] Version tag may only be applied when all projects are "clean". --- build.gradle | 3 ++- org.jdrupes.vmoperator.manager/build.gradle | 9 +++++---- org.jdrupes.vmoperator.runner.qemu/build.gradle | 17 +++++++++-------- spice-squid/build.gradle | 9 +++++---- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/build.gradle b/build.gradle index d0ebc71..df173d8 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,8 @@ buildscript { plugins { id 'org.ajoberstar.grgit' version '5.2.0' apply false id 'org.ajoberstar.git-publish' version '4.2.0' apply false - id 'pl.allegro.tech.build.axion-release' version '1.15.0' apply false + id 'pl.allegro.tech.build.axion-release' version '1.17.2' apply false + id 'org.jdrupes.vmoperator.versioning-conventions' id 'org.jdrupes.vmoperator.java-doc-conventions' id 'eclipse' id "com.github.node-gradle.node" version "7.0.1" diff --git a/org.jdrupes.vmoperator.manager/build.gradle b/org.jdrupes.vmoperator.manager/build.gradle index 45d9494..202c6f8 100644 --- a/org.jdrupes.vmoperator.manager/build.gradle +++ b/org.jdrupes.vmoperator.manager/build.gradle @@ -47,6 +47,7 @@ application { project.ext.gitBranch = grgit.branch.current.name.replace('/', '-') def registry = "${project.rootProject.properties['docker.registry']}" +def rootVersion = rootProject.version task buildImage(type: Exec) { dependsOn installDist @@ -70,7 +71,7 @@ task pushImage(type: Exec) { task tagWithVersion(type: Exec) { dependsOn pushImage - enabled = !project.version.contains("SNAPSHOT") + enabled = !rootVersion.contains("SNAPSHOT") commandLine 'podman', 'push', \ "${project.name}:${project.gitBranch}",\ @@ -80,9 +81,9 @@ task tagWithVersion(type: Exec) { task tagAsLatest(type: Exec) { dependsOn tagWithVersion - enabled = !project.version.contains("SNAPSHOT") - && !project.version.contains("alpha") \ - && !project.version.contains("beta") \ + enabled = !rootVersion.contains("SNAPSHOT") + && !rootVersion.contains("alpha") \ + && !rootVersion.contains("beta") \ || project.rootProject.properties['docker.testRegistry'] \ && project.rootProject.properties['docker.registry'] \ == project.rootProject.properties['docker.testRegistry'] diff --git a/org.jdrupes.vmoperator.runner.qemu/build.gradle b/org.jdrupes.vmoperator.runner.qemu/build.gradle index 8cea1ae..b2cd8d4 100644 --- a/org.jdrupes.vmoperator.runner.qemu/build.gradle +++ b/org.jdrupes.vmoperator.runner.qemu/build.gradle @@ -33,6 +33,7 @@ application { project.ext.gitBranch = grgit.branch.current.name.replace('/', '-') def registry = "${project.rootProject.properties['docker.registry']}" +def rootVersion = rootProject.version task buildImageArch(type: Exec) { dependsOn installDist @@ -54,7 +55,7 @@ task pushImageArch(type: Exec) { task tagWithVersionArch(type: Exec) { dependsOn pushImageArch - enabled = !project.version.contains("SNAPSHOT") + enabled = !rootVersion.contains("SNAPSHOT") commandLine 'podman', 'push', \ "${project.name}-arch:${project.gitBranch}",\ @@ -64,9 +65,9 @@ task tagWithVersionArch(type: Exec) { task tagAsLatestArch(type: Exec) { dependsOn tagWithVersionArch - enabled = !project.version.contains("SNAPSHOT") - && !project.version.contains("alpha") \ - && !project.version.contains("beta") \ + enabled = !rootVersion.contains("SNAPSHOT") + && !rootVersion.contains("alpha") \ + && !rootVersion.contains("beta") \ || project.rootProject.properties['docker.testRegistry'] \ && project.rootProject.properties['docker.registry'] \ == project.rootProject.properties['docker.testRegistry'] @@ -96,7 +97,7 @@ task pushImageAlpine(type: Exec) { task tagWithVersionAlpine(type: Exec) { dependsOn pushImageAlpine - enabled = !project.version.contains("SNAPSHOT") + enabled = !rootVersion.contains("SNAPSHOT") commandLine 'podman', 'push', \ "${project.name}-alpine:${project.gitBranch}",\ @@ -106,9 +107,9 @@ task tagWithVersionAlpine(type: Exec) { task tagAsLatestAlpine(type: Exec) { dependsOn tagWithVersionAlpine - enabled = !project.version.contains("SNAPSHOT") - && !project.version.contains("alpha") \ - && !project.version.contains("beta") \ + enabled = !rootVersion.contains("SNAPSHOT") + && !rootVersion.contains("alpha") \ + && !rootVersion.contains("beta") \ || project.rootProject.properties['docker.testRegistry'] \ && project.rootProject.properties['docker.registry'] \ == project.rootProject.properties['docker.testRegistry'] diff --git a/spice-squid/build.gradle b/spice-squid/build.gradle index e7ccd00..5278098 100644 --- a/spice-squid/build.gradle +++ b/spice-squid/build.gradle @@ -7,6 +7,7 @@ dependencies { project.ext.gitBranch = grgit.branch.current.name.replace('/', '-') def registry = "${project.rootProject.properties['docker.registry']}" +def rootVersion = rootProject.version task buildImage(type: Exec) { inputs.files 'Containerfile' @@ -26,7 +27,7 @@ task pushImage(type: Exec) { task tagWithVersion(type: Exec) { dependsOn pushImage - enabled = !project.version.contains("SNAPSHOT") + enabled = !rootVersion.contains("SNAPSHOT") commandLine 'podman', 'push', \ "${project.name}:${project.gitBranch}",\ @@ -36,9 +37,9 @@ task tagWithVersion(type: Exec) { task tagAsLatest(type: Exec) { dependsOn tagWithVersion - enabled = !project.version.contains("SNAPSHOT") - && !project.version.contains("alpha") \ - && !project.version.contains("beta") \ + enabled = !rootVersion.contains("SNAPSHOT") + && !rootVersion.contains("alpha") \ + && !rootVersion.contains("beta") \ || project.rootProject.properties['docker.testRegistry'] \ && project.rootProject.properties['docker.registry'] \ == project.rootProject.properties['docker.testRegistry'] From 63e77c0a8afaf5e00702e35eabf5b77767485199 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sun, 30 Jun 2024 15:09:20 +0200 Subject: [PATCH 170/462] Fix HTML error. --- webpages/_layouts/vm-operator.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpages/_layouts/vm-operator.html b/webpages/_layouts/vm-operator.html index dfe4220..282dde0 100644 --- a/webpages/_layouts/vm-operator.html +++ b/webpages/_layouts/vm-operator.html @@ -44,7 +44,7 @@

Upgrading

Javadoc

From 88026669442533898c439422e1e5c5efab190c1e Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 2 Jul 2024 13:39:20 +0200 Subject: [PATCH 171/462] Fix link. --- webpages/_layouts/vm-operator.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpages/_layouts/vm-operator.html b/webpages/_layouts/vm-operator.html index 282dde0..d4b4f0c 100644 --- a/webpages/_layouts/vm-operator.html +++ b/webpages/_layouts/vm-operator.html @@ -18,7 +18,7 @@
-

VM-Operator

+

VM-Operator

By Michael N. Lipp

Mastodon Follow

From 9a5e1800ffff1bee8e84636b7b89558b5cd4e407 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 4 Jul 2024 09:38:02 +0200 Subject: [PATCH 172/462] Remove duplicate h1. --- webpages/_layouts/vm-operator.html | 4 ++-- webpages/stylesheets/styles.css | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/webpages/_layouts/vm-operator.html b/webpages/_layouts/vm-operator.html index d4b4f0c..b6172f3 100644 --- a/webpages/_layouts/vm-operator.html +++ b/webpages/_layouts/vm-operator.html @@ -18,8 +18,8 @@
diff --git a/webpages/stylesheets/styles.css b/webpages/stylesheets/styles.css index 748ffcb..8d6b803 100644 --- a/webpages/stylesheets/styles.css +++ b/webpages/stylesheets/styles.css @@ -5,7 +5,7 @@ body { color:#595959; } -h1, h2, h3, h4, h5, h6 { +h1, h2, h3, h4, h5, h6, .index-title, .index-subtitle { color:#222; margin:0 0 20px; } @@ -14,11 +14,11 @@ p, ul, ol, table, pre, dl { margin:0 0 20px; } -h1, h2, h3 { +h1, h2, h3, .index-title, .index-subtitle { line-height:1.1; } -h1 { +h1, .index-title { font-size:28px; font-weight: 500; } @@ -28,11 +28,15 @@ h2 { font-weight: 500; } -h3, h4, h5, h6 { +h3, h4, h5, h6, .index-subtitle { color:#494949; font-weight: 500; } +.index-subtitle { + font-size: 1.17em; +} + a { color:#39c; text-decoration:none; From 299bded9de8974a79aeb25125f2d0394bb4523e2 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Sat, 6 Jul 2024 10:35:26 +0200 Subject: [PATCH 173/462] Add backlink. --- overview.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/overview.md b/overview.md index 0677d51..447a33c 100644 --- a/overview.md +++ b/overview.md @@ -3,5 +3,8 @@ A Kubernetes operator for running VMs as pods. VM-Operator =========== -The VM-operator is built on the [JGrapes](https://mnlipp.github.io/jgrapes/) -event driven framework. +The VM-operator enables you to easily run Qemu based VMs as pods +in Kubernetes. It is built on the +[JGrapes](https://mnlipp.github.io/jgrapes/) event driven framework. + +See the project's [home page](https://jdrupes.org/vm-operator/) for details. From f45c5698d1d0b89217b5e90c1b02fc9e4a87c3cc Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 9 Jul 2024 11:21:14 +0200 Subject: [PATCH 174/462] Update. --- README.md | 4 +- webpages/vm-operator/index-pic.svg | 7329 ++++++++++++++++++++++++++++ webpages/vm-operator/index.md | 6 +- 3 files changed, 7335 insertions(+), 4 deletions(-) create mode 100644 webpages/vm-operator/index-pic.svg diff --git a/README.md b/README.md index 52a2fa8..9118f9d 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ # Run Qemu in Kubernetes Pods -The goal of this project is to provide the means for running Qemu -based VMs in Kubernetes pods. +The goal of this project is to provide easy to use and flexible components +for running Qemu based VMs in Kubernetes pods. See the [project's home page](https://jdrupes.org/vm-operator/) for details. diff --git a/webpages/vm-operator/index-pic.svg b/webpages/vm-operator/index-pic.svg new file mode 100644 index 0000000..e912900 --- /dev/null +++ b/webpages/vm-operator/index-pic.svg @@ -0,0 +1,7329 @@ + + + + diff --git a/webpages/vm-operator/index.md b/webpages/vm-operator/index.md index 232a9ce..b5e18ca 100644 --- a/webpages/vm-operator/index.md +++ b/webpages/vm-operator/index.md @@ -6,8 +6,10 @@ layout: vm-operator # Welcome to VM-Operator -The goal of this project is to provide the means for running Qemu -based VMs in Kubernetes pods. +![Overview picture](index-pic.svg) + +The goal of this project is to provide easy to use and flexible components +for running Qemu based VMs in Kubernetes pods. The image used for the VM pods combines Qemu and a control program for starting and managing the Qemu process. This application is called From 8f8a38771ec9d1cac0b1a0d3013f6a8b71430f60 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 9 Jul 2024 17:09:52 +0200 Subject: [PATCH 175/462] Use longer titles. --- webpages/vm-operator/admin-gui.md | 2 +- webpages/vm-operator/manager.md | 2 +- webpages/vm-operator/runner.md | 2 +- webpages/vm-operator/user-gui.md | 2 +- webpages/vm-operator/webgui.md | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/webpages/vm-operator/admin-gui.md b/webpages/vm-operator/admin-gui.md index 15a6dec..f7ce987 100644 --- a/webpages/vm-operator/admin-gui.md +++ b/webpages/vm-operator/admin-gui.md @@ -1,5 +1,5 @@ --- -title: VM-Operator Web-GUI for Admins +title: VM-Operator: Administrator View — Provides an overview of running VMs layout: vm-operator --- diff --git a/webpages/vm-operator/manager.md b/webpages/vm-operator/manager.md index 4613201..7f3f8e3 100644 --- a/webpages/vm-operator/manager.md +++ b/webpages/vm-operator/manager.md @@ -1,5 +1,5 @@ --- -title: VM-Operator Manager +title: VM-Operator: The Manager — Reconciles CRDs and provides a Web-GUI layout: vm-operator --- diff --git a/webpages/vm-operator/runner.md b/webpages/vm-operator/runner.md index d580530..260007b 100644 --- a/webpages/vm-operator/runner.md +++ b/webpages/vm-operator/runner.md @@ -1,5 +1,5 @@ --- -title: VM-Operator Runner +title: VM-Operator: The Runner — Starts and monitors a VM layout: vm-operator --- diff --git a/webpages/vm-operator/user-gui.md b/webpages/vm-operator/user-gui.md index 394c28f..a084e3b 100644 --- a/webpages/vm-operator/user-gui.md +++ b/webpages/vm-operator/user-gui.md @@ -1,5 +1,5 @@ --- -title: VM-Operator Web-GUI for Users +title: VM-Operator: User View — Allows users to manage their own VMs layout: vm-operator --- diff --git a/webpages/vm-operator/webgui.md b/webpages/vm-operator/webgui.md index 38b9faa..9621101 100644 --- a/webpages/vm-operator/webgui.md +++ b/webpages/vm-operator/webgui.md @@ -1,5 +1,5 @@ --- -title: VM-Operator Web-GUI +title: VM-Operator: Web-GUI — Provides easy access to VM management layout: vm-operator --- From fcf0c1d1af45889c66f10c54639969a90a613c1e Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 9 Jul 2024 17:19:15 +0200 Subject: [PATCH 176/462] Fix syntax. --- webpages/vm-operator/admin-gui.md | 2 +- webpages/vm-operator/controller.md | 2 +- webpages/vm-operator/manager.md | 2 +- webpages/vm-operator/runner.md | 2 +- webpages/vm-operator/user-gui.md | 2 +- webpages/vm-operator/webgui.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/webpages/vm-operator/admin-gui.md b/webpages/vm-operator/admin-gui.md index f7ce987..4bfa3d3 100644 --- a/webpages/vm-operator/admin-gui.md +++ b/webpages/vm-operator/admin-gui.md @@ -1,5 +1,5 @@ --- -title: VM-Operator: Administrator View — Provides an overview of running VMs +title: "VM-Operator: Administrator View — Provides an overview of running VMs" layout: vm-operator --- diff --git a/webpages/vm-operator/controller.md b/webpages/vm-operator/controller.md index 2a00b16..ce94228 100644 --- a/webpages/vm-operator/controller.md +++ b/webpages/vm-operator/controller.md @@ -1,5 +1,5 @@ --- -title: VM-Operator Controller +title: "VM-Operator Controller — Reconciles the VM CRs" layout: vm-operator --- diff --git a/webpages/vm-operator/manager.md b/webpages/vm-operator/manager.md index 7f3f8e3..a4ed202 100644 --- a/webpages/vm-operator/manager.md +++ b/webpages/vm-operator/manager.md @@ -1,5 +1,5 @@ --- -title: VM-Operator: The Manager — Reconciles CRDs and provides a Web-GUI +title: "VM-Operator: The Manager — Reconciles CRDs and provides a Web-GUI" layout: vm-operator --- diff --git a/webpages/vm-operator/runner.md b/webpages/vm-operator/runner.md index 260007b..319a5dc 100644 --- a/webpages/vm-operator/runner.md +++ b/webpages/vm-operator/runner.md @@ -1,5 +1,5 @@ --- -title: VM-Operator: The Runner — Starts and monitors a VM +title: "VM-Operator: The Runner — Starts and monitors a VM" layout: vm-operator --- diff --git a/webpages/vm-operator/user-gui.md b/webpages/vm-operator/user-gui.md index a084e3b..03e7bdf 100644 --- a/webpages/vm-operator/user-gui.md +++ b/webpages/vm-operator/user-gui.md @@ -1,5 +1,5 @@ --- -title: VM-Operator: User View — Allows users to manage their own VMs +title: "VM-Operator: User View — Allows users to manage their own VMs" layout: vm-operator --- diff --git a/webpages/vm-operator/webgui.md b/webpages/vm-operator/webgui.md index 9621101..f354770 100644 --- a/webpages/vm-operator/webgui.md +++ b/webpages/vm-operator/webgui.md @@ -1,5 +1,5 @@ --- -title: VM-Operator: Web-GUI — Provides easy access to VM management +title: "VM-Operator: Web-GUI — Provides easy access to VM management" layout: vm-operator --- From e3fc4747e490a375d0c22990f36251ea3f61dcb2 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Tue, 9 Jul 2024 17:31:58 +0200 Subject: [PATCH 177/462] Minor edits. --- webpages/vm-operator/controller.md | 2 +- webpages/vm-operator/manager.md | 2 +- webpages/vm-operator/upgrading.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/webpages/vm-operator/controller.md b/webpages/vm-operator/controller.md index ce94228..a24943d 100644 --- a/webpages/vm-operator/controller.md +++ b/webpages/vm-operator/controller.md @@ -1,5 +1,5 @@ --- -title: "VM-Operator Controller — Reconciles the VM CRs" +title: "VM-Operator: Controller — Reconciles the VM CRs" layout: vm-operator --- diff --git a/webpages/vm-operator/manager.md b/webpages/vm-operator/manager.md index a4ed202..e4e5442 100644 --- a/webpages/vm-operator/manager.md +++ b/webpages/vm-operator/manager.md @@ -1,5 +1,5 @@ --- -title: "VM-Operator: The Manager — Reconciles CRDs and provides a Web-GUI" +title: "VM-Operator: The Manager — Provides the controller and a Web-GUI" layout: vm-operator --- diff --git a/webpages/vm-operator/upgrading.md b/webpages/vm-operator/upgrading.md index 41d04ad..7b69716 100644 --- a/webpages/vm-operator/upgrading.md +++ b/webpages/vm-operator/upgrading.md @@ -1,5 +1,5 @@ --- -title: Upgrading +title: "VM-Operator: Upgrading — Issues to watch out for" layout: vm-operator --- From 33856fffc28b6c19f58a6522d84520be9df328ce Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 11 Jul 2024 12:52:28 +0200 Subject: [PATCH 178/462] Fix link. --- webpages/_layouts/vm-operator.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpages/_layouts/vm-operator.html b/webpages/_layouts/vm-operator.html index b6172f3..9e9cbb4 100644 --- a/webpages/_layouts/vm-operator.html +++ b/webpages/_layouts/vm-operator.html @@ -18,7 +18,7 @@
- +

Mastodon Follow

From e7cdaea205e900dbfebc7616ac2849a8f02e5183 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 11 Jul 2024 12:54:12 +0200 Subject: [PATCH 179/462] Minor edit. --- webpages/vm-operator/user-gui.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpages/vm-operator/user-gui.md b/webpages/vm-operator/user-gui.md index 03e7bdf..ce46e8f 100644 --- a/webpages/vm-operator/user-gui.md +++ b/webpages/vm-operator/user-gui.md @@ -99,7 +99,7 @@ spec: spice: port: 5930 server: 192.168.19.32 - proxyUrl: http://lgpe-spice.some.host:1234 + proxyUrl: http://vms-spice.some.host:1234 generateSecret: true ``` From 1b2d7ec330e60282c5d7b7b4ea4458ed98413b62 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Thu, 11 Jul 2024 13:21:46 +0200 Subject: [PATCH 180/462] Fix link. --- webpages/_layouts/vm-operator.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webpages/_layouts/vm-operator.html b/webpages/_layouts/vm-operator.html index 9e9cbb4..7ea1bc5 100644 --- a/webpages/_layouts/vm-operator.html +++ b/webpages/_layouts/vm-operator.html @@ -18,8 +18,8 @@
From a62b8c2899ac04d2960957902478c23188c77191 Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Mon, 15 Jul 2024 08:46:23 +0200 Subject: [PATCH 181/462] Update index.md --- webpages/vm-operator/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webpages/vm-operator/index.md b/webpages/vm-operator/index.md index b5e18ca..32d2cd5 100644 --- a/webpages/vm-operator/index.md +++ b/webpages/vm-operator/index.md @@ -1,6 +1,6 @@ --- -title: VM-Operator by mnlipp -description: A Kubernetes operator for running virtual machines (notably Qemu VMs) in pods on Kubernetes +title: Run Qemu based VMs on Kubernetes +description: A Kubernetes operator for running virtual machines (notably Qemu VMs) in pods on Kubernetes with a web interface for admins and users. layout: vm-operator --- From 959a35ca9e729a1778376eeca8b26beeabd7d6af Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Wed, 17 Jul 2024 07:52:03 +0200 Subject: [PATCH 182/462] Update manager.md --- webpages/vm-operator/manager.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webpages/vm-operator/manager.md b/webpages/vm-operator/manager.md index e4e5442..8e6ebb4 100644 --- a/webpages/vm-operator/manager.md +++ b/webpages/vm-operator/manager.md @@ -1,5 +1,5 @@ --- -title: "VM-Operator: The Manager — Provides the controller and a Web-GUI" +title: "VM-Operator: The Manager — Provides the controller and a web user interface" layout: vm-operator --- @@ -7,7 +7,7 @@ layout: vm-operator The Manager is the program that provides the controller from the [operator pattern](https://github.com/cncf/tag-app-delivery/blob/eece8f7307f2970f46f100f51932db106db46968/operator-wg/whitepaper/Operator-WhitePaper_v1-0.md#operator-components-in-kubernetes) -together with a Web-GUI. It should be run in a container in the cluster. +together with a web user interface. It should be run in a container in the cluster. ## Installation From 45e271e6d0b482ab5a994b2eadcc6f1a1c35d86b Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" <1446020+mnlipp@users.noreply.github.com> Date: Wed, 17 Jul 2024 07:55:16 +0200 Subject: [PATCH 183/462] Update webgui.md --- webpages/vm-operator/webgui.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webpages/vm-operator/webgui.md b/webpages/vm-operator/webgui.md index f354770..ef24245 100644 --- a/webpages/vm-operator/webgui.md +++ b/webpages/vm-operator/webgui.md @@ -1,11 +1,11 @@ --- -title: "VM-Operator: Web-GUI — Provides easy access to VM management" +title: "VM-Operator: Web user interface — Provides easy access to VM management" layout: vm-operator --- # The Web-GUI -The manager component provides a GUI via a web server. The web GUI is +The manager component provides a GUI via a web server. This web user interface is implemented using components from the [JGrapes WebConsole](https://jgrapes.org/WebConsole.html) project. Configuration of the GUI therefore follows the conventions @@ -31,7 +31,7 @@ from the ## User Access -Access to the web GUI is controlled by the login conlet. The framework +Access to the web user interface is controlled by the login conlet. The framework does not include sophisticated components for user management. Rather, it assumes that an OIDC provider is responsible for user authentication and role management. From 4c04bb0e0ace6a6a4e8cab7803c34261344c1d1a Mon Sep 17 00:00:00 2001 From: "Michael N. Lipp" Date: Wed, 17 Jul 2024 22:26:29 +0200 Subject: [PATCH 184/462] Minor edit. --- webpages/_layouts/vm-operator.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpages/_layouts/vm-operator.html b/webpages/_layouts/vm-operator.html index 7ea1bc5..30e6407 100644 --- a/webpages/_layouts/vm-operator.html +++ b/webpages/_layouts/vm-operator.html @@ -40,7 +40,7 @@ -

The Web-GUI

+

Web interface