Merge branch 'release/v3.1.x' into release/v3.x
6
.github/workflows/gradle.yml
vendored
|
|
@ -22,10 +22,10 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install graphviz
|
- name: Install graphviz
|
||||||
run: sudo apt-get install graphviz
|
run: sudo apt-get install graphviz
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew -Prepo.access.token=${{ secrets.REPO_ACCESS_TOKEN }} stage
|
run: ./gradlew -Pwebsite.push.token=${{ secrets.WEBSITE_PUSH_TOKEN }} stage
|
||||||
|
|
|
||||||
6
.github/workflows/release.yml
vendored
|
|
@ -31,10 +31,10 @@ jobs:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
- name: Push with Gradle
|
- 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
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,76 @@
|
||||||
default:
|
stages:
|
||||||
# Template project: https://gitlab.com/pages/jekyll
|
- build
|
||||||
# Docs: https://docs.gitlab.com/ee/pages/
|
- test
|
||||||
image: ruby:3.2
|
- 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:
|
before_script:
|
||||||
- git fetch origin gh-pages
|
- git fetch origin gh-pages
|
||||||
- git checkout gh-pages
|
- git checkout gh-pages
|
||||||
- gem install bundler
|
- gem install bundler
|
||||||
- bundle install
|
- bundle install
|
||||||
variables:
|
|
||||||
JEKYLL_ENV: production
|
test-pages:
|
||||||
LC_ALL: C.UTF-8
|
|
||||||
test:
|
|
||||||
stage: test
|
stage: test
|
||||||
|
extends: .pages-job
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_BRANCH == "gh-pages"
|
||||||
script:
|
script:
|
||||||
- bundle exec jekyll build -d test
|
- bundle exec jekyll build -d test
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- test
|
- test
|
||||||
|
|
||||||
pages:
|
#publish-pages:
|
||||||
stage: deploy
|
# stage: publish
|
||||||
script:
|
# extends: .pages-job
|
||||||
- bundle exec jekyll build -d public
|
# rules:
|
||||||
artifacts:
|
# - if: $CI_COMMIT_BRANCH == "gh-pages"
|
||||||
paths:
|
# script:
|
||||||
- public
|
# - bundle exec jekyll build -d public
|
||||||
environment: production
|
# artifacts:
|
||||||
|
# paths:
|
||||||
|
# - public
|
||||||
|
# environment: production
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,5 @@
|
||||||
The goal of this project is to provide the means for running Qemu
|
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/)
|
See the [project's home page](https://jdrupes.org/vm-operator/)
|
||||||
for details.
|
for details.
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ task stage {
|
||||||
tc -> tc.findByName("build") }.flatten()
|
tc -> tc.findByName("build") }.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JavaVersion.current() == JavaVersion.VERSION_17) {
|
if (JavaVersion.current() == JavaVersion.VERSION_21) {
|
||||||
// Publish JavaDoc
|
// Publish JavaDoc
|
||||||
dependsOn gitPublishPush
|
dependsOn gitPublishPush
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ sourceSets {
|
||||||
|
|
||||||
java {
|
java {
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(17)
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,31 +22,28 @@ configurations {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
markdownDoclet "org.jdrupes.mdoclet:doclet:3.1.0"
|
markdownDoclet "org.jdrupes.mdoclet:doclet:4.0.0"
|
||||||
javadocTaglets "org.jdrupes.taglets:plantuml-taglet:2.1.0"
|
javadocTaglets "org.jdrupes.taglets:plantuml-taglet:3.0.0"
|
||||||
}
|
|
||||||
|
|
||||||
task javadocResources(type: Copy) {
|
|
||||||
into file(docDestinationDir)
|
|
||||||
from ("${rootProject.rootDir}/misc") {
|
|
||||||
include '*.woff2'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task apidocs (type: JavaExec) {
|
task apidocs (type: JavaExec) {
|
||||||
// Does not work on JitPack, no /usr/bin/dot
|
// Does not work on JitPack, no /usr/bin/dot
|
||||||
enabled = JavaVersion.current() == JavaVersion.VERSION_17
|
enabled = JavaVersion.current() == JavaVersion.VERSION_21
|
||||||
|
|
||||||
dependsOn javadocResources
|
|
||||||
|
|
||||||
outputs.dir(docDestinationDir)
|
outputs.dir(docDestinationDir)
|
||||||
|
|
||||||
inputs.file rootProject.file('overview.md')
|
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',
|
jvmArgs = ['--add-exports=jdk.compiler/com.sun.tools.doclint=ALL-UNNAMED',
|
||||||
'--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED']
|
'--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
|
||||||
main = 'jdk.javadoc.internal.tool.Main'
|
'--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 {
|
gradle.projectsEvaluated {
|
||||||
// Make sure that other projects' compileClasspaths are resolved
|
// Make sure that other projects' compileClasspaths are resolved
|
||||||
|
|
@ -69,8 +66,8 @@ task apidocs (type: JavaExec) {
|
||||||
'-package',
|
'-package',
|
||||||
'-use',
|
'-use',
|
||||||
'-linksource',
|
'-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://jgrapes.org/latest-release/javadoc/',
|
||||||
'-link', 'https://freemarker.apache.org/docs/api/',
|
'-link', 'https://freemarker.apache.org/docs/api/',
|
||||||
'--add-exports', 'jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
|
'--add-exports', 'jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
|
||||||
'--add-exports', 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
|
'--add-exports', 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
|
||||||
|
|
@ -88,7 +85,7 @@ task apidocs (type: JavaExec) {
|
||||||
'-bottom', rootProject.file("misc/javadoc.bottom.txt").text,
|
'-bottom', rootProject.file("misc/javadoc.bottom.txt").text,
|
||||||
'--allow-script-in-comments',
|
'--allow-script-in-comments',
|
||||||
'-Xdoclint:-html',
|
'-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',
|
'--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.formats.html=ALL-UNNAMED',
|
||||||
'-quiet'
|
'-quiet'
|
||||||
]
|
]
|
||||||
|
|
@ -97,23 +94,46 @@ task apidocs (type: JavaExec) {
|
||||||
ignoreExitValue true
|
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
|
// Prepare github authentication for plugins
|
||||||
if (System.properties['org.ajoberstar.grgit.auth.username'] == null) {
|
if (System.properties['org.ajoberstar.grgit.auth.username'] == null) {
|
||||||
System.setProperty('org.ajoberstar.grgit.auth.username',
|
System.setProperty('org.ajoberstar.grgit.auth.username',
|
||||||
project.rootProject.properties['repo.access.token'] ?: "nouser")
|
project.rootProject.properties['website.push.token'] ?: "nouser")
|
||||||
}
|
}
|
||||||
|
|
||||||
gitPublish {
|
gitPublish {
|
||||||
repoUri = 'https://github.com/mnlipp/VM-Operator.git'
|
repoUri = 'https://github.com/mnlipp/jdrupes.org.git'
|
||||||
branch = 'gh-pages'
|
branch = 'main'
|
||||||
contents {
|
contents {
|
||||||
|
from("${rootProject.projectDir}/webpages") {
|
||||||
|
include '_layouts/vm-operator.html'
|
||||||
|
include 'vm-operator/**'
|
||||||
|
}
|
||||||
from("${rootProject.buildDir}/javadoc") {
|
from("${rootProject.buildDir}/javadoc") {
|
||||||
into 'javadoc'
|
into 'vm-operator/javadoc'
|
||||||
}
|
}
|
||||||
if (!findProject(':org.jdrupes.vmoperator.runner.qemu').isSnapshot
|
if (!findProject(':org.jdrupes.vmoperator.runner.qemu').isSnapshot
|
||||||
&& !findProject(':org.jdrupes.vmoperator.manager').isSnapshot) {
|
&& !findProject(':org.jdrupes.vmoperator.manager').isSnapshot) {
|
||||||
from("${rootProject.buildDir}/javadoc") {
|
from("${rootProject.buildDir}/javadoc") {
|
||||||
into 'latest-release/javadoc'
|
into 'vm-operator/latest-release/javadoc'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ spec:
|
||||||
containers:
|
containers:
|
||||||
- name: vm-operator
|
- name: vm-operator
|
||||||
image: >-
|
image: >-
|
||||||
ghcr.io/mnlipp/org.jdrupes.vmoperator.manager:3.0.0
|
ghcr.io/mnlipp/org.jdrupes.vmoperator.manager:3.1.1
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: config
|
- name: config
|
||||||
mountPath: /etc/opt/vmoperator
|
mountPath: /etc/opt/vmoperator
|
||||||
|
|
|
||||||
1
gradle.properties
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
org.gradle.parallel=true
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,7 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
||||||
31
gradlew
vendored
|
|
@ -55,7 +55,7 @@
|
||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (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.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
|
@ -83,10 +83,8 @@ done
|
||||||
# This is normally unused
|
# This is normally unused
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# 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
|
||||||
# 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"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
|
@ -133,10 +131,13 @@ location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
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
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
|
|
@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
# 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 ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
|
|
@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
# 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" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
|
|
@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
# double quotes to make sure that they get re-expanded; and
|
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
# 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 -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
|
|
||||||
20
gradlew.bat
vendored
|
|
@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|
@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|
|
||||||
2
misc/javadoc-overwrites.css
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
:root { --body-font-size: 16px;}
|
||||||
|
:root { --code-font-size: 16px;}
|
||||||
|
|
@ -4,26 +4,30 @@
|
||||||
<a href="https://github.com/site/terms" target="_top">Terms</a>
|
<a href="https://github.com/site/terms" target="_top">Terms</a>
|
||||||
— <a href="https://github.com/site/privacy" target="_top">Privacy</a></p>
|
— <a href="https://github.com/site/privacy" target="_top">Privacy</a></p>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
if (location.hostname.indexOf("github") !== -1) {
|
if (location.hostname.indexOf("github") !== -1 || location.hostname.indexOf("jdrupes.org") !== -1) {
|
||||||
document.getElementById("githubfooter").style.visibility="visible";
|
document.getElementById("githubfooter").style.visibility="visible";
|
||||||
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(['disableCookies']);
|
|
||||||
_paq.push(['trackPageView']);
|
|
||||||
_paq.push(['enableLinkTracking']);
|
|
||||||
(function() {
|
|
||||||
var u="//piwik.mnl.de/";
|
|
||||||
_paq.push(['setTrackerUrl', u+'piwik.php']);
|
|
||||||
_paq.push(['setSiteId', '14']);
|
|
||||||
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);
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<noscript>
|
<noscript>
|
||||||
<div>JavaScript is disabled on your browser, terms and privacy links may not be shown correctly.</div>
|
<div>JavaScript is disabled on your browser, terms and privacy links may not be shown correctly.</div>
|
||||||
</noscript>
|
</noscript>
|
||||||
|
<!-- Matomo anonymous, no cookies (https://matomo.org/blog/2018/04/how-to-not-process-any-personal-data-with-matomo-and-what-it-means-for-you/) -->
|
||||||
|
<script>
|
||||||
|
var _paq = _paq || [];
|
||||||
|
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||||
|
_paq.push(["setDocumentTitle", document.domain + "/" + document.title]);
|
||||||
|
_paq.push(["setCookieDomain", "*.jdrupes.org"]);
|
||||||
|
_paq.push(['disableCookies']);
|
||||||
|
_paq.push(['trackPageView']);
|
||||||
|
_paq.push(['enableLinkTracking']);
|
||||||
|
(function() {
|
||||||
|
var u="//jdrupes.org/";
|
||||||
|
_paq.push(['setTrackerUrl', u+'piwik.php']);
|
||||||
|
_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);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<noscript><p><img referrerpolicy="no-referrer-when-downgrade" src="//piwik.mnl.de/matomo.php?idsite=15&rec=1" style="border:0;" alt="" /></p></noscript>
|
||||||
|
<!-- End Matomo Code -->
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -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, \
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="768" height="768">\
|
|
||||||
<path d="M584 664H104V184h216V80H0v688h688V448H584zM384 0l132 \
|
|
||||||
132-240 240 120 120 240-240 132 132V0z" fill="%234a6782"/>\
|
|
||||||
</svg>');
|
|
||||||
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, \
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="768" height="768">\
|
|
||||||
<path d="M584 664H104V184h216V80H0v688h688V448H584zM384 0l132 \
|
|
||||||
132-240 240 120 120 240-240 132 132V0z" fill="%23bb7a2a"/>\
|
|
||||||
</svg>');
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -45,45 +45,36 @@ application {
|
||||||
mainClass = 'org.jdrupes.vmoperator.manager.Manager'
|
mainClass = 'org.jdrupes.vmoperator.manager.Manager'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
project.ext.gitBranch = grgit.branch.current.name.replace('/', '-')
|
||||||
|
|
||||||
task buildImage(type: Exec) {
|
task buildImage(type: Exec) {
|
||||||
dependsOn installDist
|
dependsOn installDist
|
||||||
inputs.files 'src/org/jdrupes/vmoperator/manager/Containerfile'
|
inputs.files 'src/org/jdrupes/vmoperator/manager/Containerfile'
|
||||||
|
|
||||||
commandLine 'podman', 'build', '--pull',
|
commandLine 'podman', 'build', '--pull',
|
||||||
'-t', "${project.name}:${project.version}",\
|
'-t', "${project.name}:${project.gitBranch}",\
|
||||||
'-f', 'src/org/jdrupes/vmoperator/manager/Containerfile', '.'
|
'-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) {
|
task pushImage(type: Exec) {
|
||||||
dependsOn buildImage
|
dependsOn buildImage
|
||||||
|
// Don't push without testing first
|
||||||
|
dependsOn test
|
||||||
|
|
||||||
|
def registry = "${project.rootProject.properties['docker.registry']}"
|
||||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
commandLine 'podman', 'push', '--tls-verify=false', \
|
||||||
"localhost/${project.name}:${project.version}", \
|
"localhost/${project.name}:${project.gitBranch}", \
|
||||||
"${project.rootProject.properties['docker.registry']}" \
|
"${registry}/${project.name}:${project.gitBranch}"
|
||||||
+ "/${project.name}:${project.version}"
|
|
||||||
|
if (!project.version.contains("SNAPSHOT")) {
|
||||||
|
commandLine 'podman', 'tag', \
|
||||||
|
"${registry}/${project.name}:${project.gitBranch}",\
|
||||||
|
"${registry}/${project.name}:${project.version}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task pushLatestImage(type: Exec) {
|
task tagAsLatest(type: Exec) {
|
||||||
dependsOn buildLatestImage
|
dependsOn pushImage
|
||||||
|
|
||||||
enabled = !project.version.contains("SNAPSHOT")
|
enabled = !project.version.contains("SNAPSHOT")
|
||||||
&& !project.version.contains("alpha") \
|
&& !project.version.contains("alpha") \
|
||||||
|
|
@ -92,28 +83,21 @@ task pushLatestImage(type: Exec) {
|
||||||
&& project.rootProject.properties['docker.registry'] \
|
&& project.rootProject.properties['docker.registry'] \
|
||||||
== project.rootProject.properties['docker.testRegistry']
|
== project.rootProject.properties['docker.testRegistry']
|
||||||
|
|
||||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
def registry = "${project.rootProject.properties['docker.registry']}"
|
||||||
"localhost/${project.name}:${project.version}", \
|
commandLine 'podman', 'tag', \
|
||||||
"${project.rootProject.properties['docker.registry']}" \
|
"${registry}/${project.name}:${project.version}",\
|
||||||
+ "/${project.name}:latest"
|
"${registry}/${project.name}:latest"
|
||||||
}
|
}
|
||||||
|
|
||||||
task pushForTest(type: Exec) {
|
task pushForTest(type: Exec) {
|
||||||
dependsOn buildImage
|
dependsOn buildImage
|
||||||
|
|
||||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
commandLine 'podman', 'push', '--tls-verify=false', \
|
||||||
"localhost/${project.name}:${project.version}", \
|
"localhost/${project.name}:${project.gitBranch}", \
|
||||||
"${project.rootProject.properties['docker.registry']}" \
|
"${project.rootProject.properties['docker.testRegistry']}" \
|
||||||
+ "/${project.name}:test"
|
+ "/${project.name}:test"
|
||||||
}
|
}
|
||||||
|
|
||||||
task pushImages {
|
|
||||||
// Don't push without testing first
|
|
||||||
dependsOn test
|
|
||||||
dependsOn pushImage
|
|
||||||
dependsOn pushLatestImage
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
test {
|
||||||
enabled = project.hasProperty("k8s.testCluster")
|
enabled = project.hasProperty("k8s.testCluster")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
COPY build/install/vm-manager /opt/vmmanager
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -184,12 +184,8 @@ public class Reconciler extends Component {
|
||||||
* @param event the event
|
* @param event the event
|
||||||
* @param channel the channel
|
* @param channel the channel
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
* @throws IOException
|
* @throws TemplateException the template exception
|
||||||
* @throws ParseException
|
* @throws IOException Signals that an I/O exception has occurred.
|
||||||
* @throws MalformedTemplateNameException
|
|
||||||
* @throws TemplateNotFoundException
|
|
||||||
* @throws TemplateException
|
|
||||||
* @throws KubectlException
|
|
||||||
*/
|
*/
|
||||||
@Handler
|
@Handler
|
||||||
@SuppressWarnings("PMD.ConfusingTernary")
|
@SuppressWarnings("PMD.ConfusingTernary")
|
||||||
|
|
|
||||||
|
|
@ -31,45 +31,34 @@ application {
|
||||||
mainClass = 'org.jdrupes.vmoperator.runner.qemu.Runner'
|
mainClass = 'org.jdrupes.vmoperator.runner.qemu.Runner'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
project.ext.gitBranch = grgit.branch.current.name.replace('/', '-')
|
||||||
|
|
||||||
task buildArchImage(type: Exec) {
|
task buildArchImage(type: Exec) {
|
||||||
dependsOn installDist
|
dependsOn installDist
|
||||||
inputs.files 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch'
|
inputs.files 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch'
|
||||||
|
|
||||||
commandLine 'podman', 'build', '--pull',
|
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', '.'
|
'-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) {
|
task pushArchImage(type: Exec) {
|
||||||
dependsOn buildArchImage
|
dependsOn buildArchImage
|
||||||
|
|
||||||
|
def registry = "${project.rootProject.properties['docker.registry']}"
|
||||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
commandLine 'podman', 'push', '--tls-verify=false', \
|
||||||
"localhost/${project.name}-arch:${project.version}", \
|
"localhost/${project.name}-arch:${project.gitBranch}", \
|
||||||
"${project.rootProject.properties['docker.registry']}" \
|
"${registry}/${project.name}-arch:${project.gitBranch}"
|
||||||
+ "/${project.name}-arch:${project.version}"
|
|
||||||
|
if (!project.version.contains("SNAPSHOT")) {
|
||||||
|
commandLine 'podman', 'tag', \
|
||||||
|
"${registry}/${project.name}-arch:${project.gitBranch}",\
|
||||||
|
"${registry}/${project.name}-arch:${project.version}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task pushArchLatestImage(type: Exec) {
|
task tagAsLatestArch(type: Exec) {
|
||||||
dependsOn buildLatestArchImage
|
dependsOn pushArchImage
|
||||||
|
|
||||||
enabled = !project.version.contains("SNAPSHOT")
|
enabled = !project.version.contains("SNAPSHOT")
|
||||||
&& !project.version.contains("alpha") \
|
&& !project.version.contains("alpha") \
|
||||||
|
|
@ -78,10 +67,10 @@ task pushArchLatestImage(type: Exec) {
|
||||||
&& project.rootProject.properties['docker.registry'] \
|
&& project.rootProject.properties['docker.registry'] \
|
||||||
== project.rootProject.properties['docker.testRegistry']
|
== project.rootProject.properties['docker.testRegistry']
|
||||||
|
|
||||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
def registry = "${project.rootProject.properties['docker.registry']}"
|
||||||
"localhost/${project.name}-arch:${project.version}", \
|
commandLine 'podman', 'tag', \
|
||||||
"${project.rootProject.properties['docker.registry']}" \
|
"${registry}/${project.name}-arch:${project.version}",\
|
||||||
+ "/${project.name}-arch:latest"
|
"${registry}/${project.name}-arch:latest"
|
||||||
}
|
}
|
||||||
|
|
||||||
task buildAlpineImage(type: Exec) {
|
task buildAlpineImage(type: Exec) {
|
||||||
|
|
@ -89,40 +78,27 @@ task buildAlpineImage(type: Exec) {
|
||||||
inputs.files 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine'
|
inputs.files 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine'
|
||||||
|
|
||||||
commandLine 'podman', 'build', '--pull',
|
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', '.'
|
'-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) {
|
task pushAlpineImage(type: Exec) {
|
||||||
dependsOn buildAlpineImage
|
dependsOn buildAlpineImage
|
||||||
|
|
||||||
|
def registry = "${project.rootProject.properties['docker.registry']}"
|
||||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
commandLine 'podman', 'push', '--tls-verify=false', \
|
||||||
"localhost/${project.name}-alpine:${project.version}", \
|
"localhost/${project.name}-alpine:${project.gitBranch}", \
|
||||||
"${project.rootProject.properties['docker.registry']}" \
|
"${registry}/${project.name}-alpine:${project.gitBranch}"
|
||||||
+ "/${project.name}-alpine:${project.version}"
|
|
||||||
|
if (!project.version.contains("SNAPSHOT")) {
|
||||||
|
commandLine 'podman', 'tag', \
|
||||||
|
"${registry}/${project.name}-alpine:${project.gitBranch}",\
|
||||||
|
"${registry}/${project.name}-alpine:${project.version}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task pushAlpineLatestImage(type: Exec) {
|
task tagAsLatestAlpine(type: Exec) {
|
||||||
dependsOn buildLatestAlpineImage
|
dependsOn pushAlpineImage
|
||||||
|
|
||||||
enabled = !project.version.contains("SNAPSHOT")
|
enabled = !project.version.contains("SNAPSHOT")
|
||||||
&& !project.version.contains("alpha") \
|
&& !project.version.contains("alpha") \
|
||||||
|
|
@ -131,16 +107,19 @@ task pushAlpineLatestImage(type: Exec) {
|
||||||
&& project.rootProject.properties['docker.registry'] \
|
&& project.rootProject.properties['docker.registry'] \
|
||||||
== project.rootProject.properties['docker.testRegistry']
|
== project.rootProject.properties['docker.testRegistry']
|
||||||
|
|
||||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
def registry = "${project.rootProject.properties['docker.registry']}"
|
||||||
"localhost/${project.name}-alpine:${project.version}", \
|
commandLine 'podman', 'tag', \
|
||||||
"${project.rootProject.properties['docker.registry']}" \
|
"${registry}/${project.name}-alpine:${project.version}",\
|
||||||
+ "/${project.name}-alpine:latest"
|
"${registry}/${project.name}-alpine:latest"
|
||||||
}
|
}
|
||||||
|
|
||||||
task pushImages {
|
task pushImage {
|
||||||
dependsOn pushArchImage
|
dependsOn pushArchImage
|
||||||
dependsOn pushArchLatestImage
|
|
||||||
dependsOn pushAlpineImage
|
dependsOn pushAlpineImage
|
||||||
dependsOn pushAlpineLatestImage
|
}
|
||||||
|
|
||||||
|
task tagAsLatest {
|
||||||
|
dependsOn tagAsLatestArch
|
||||||
|
dependsOn tagAsLatestAlpine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.commands.QmpRemoveMedium;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
|
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
|
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.jdrupes.vmoperator.runner.qemu.events.TrayMovedEvent;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
import org.jgrapes.core.Component;
|
import org.jgrapes.core.Component;
|
||||||
|
|
@ -69,7 +69,7 @@ public class CdMediaController extends Component {
|
||||||
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
|
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
|
||||||
"PMD.AvoidInstantiatingObjectsInLoops" })
|
"PMD.AvoidInstantiatingObjectsInLoops" })
|
||||||
public void onConfigureQemu(ConfigureQemu event) {
|
public void onConfigureQemu(ConfigureQemu event) {
|
||||||
if (event.state() == State.TERMINATING) {
|
if (event.runState() == RunState.TERMINATING) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ public class CdMediaController extends Component {
|
||||||
}
|
}
|
||||||
var driveId = "cd" + cdCounter++;
|
var driveId = "cd" + cdCounter++;
|
||||||
var newFile = Optional.ofNullable(drives[i].file).orElse("");
|
var newFile = Optional.ofNullable(drives[i].file).orElse("");
|
||||||
if (event.state() == State.STARTING) {
|
if (event.runState() == RunState.STARTING) {
|
||||||
current.put(driveId, newFile);
|
current.put(driveId, newFile);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -116,8 +116,8 @@ public class CdMediaController extends Component {
|
||||||
*/
|
*/
|
||||||
@Handler
|
@Handler
|
||||||
public void onTrayMovedEvent(TrayMovedEvent event) {
|
public void onTrayMovedEvent(TrayMovedEvent event) {
|
||||||
trayState.put(event.driveId(), event.state());
|
trayState.put(event.driveId(), event.trayState());
|
||||||
if (event.state() == TrayState.OPEN
|
if (event.trayState() == TrayState.OPEN
|
||||||
&& pending.containsKey(event.driveId())) {
|
&& pending.containsKey(event.driveId())) {
|
||||||
changeMedium(event.driveId());
|
changeMedium(event.driveId());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ FROM docker.io/alpine
|
||||||
|
|
||||||
RUN apk update
|
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
|
RUN mkdir -p /etc/qemu && echo "allow all" > /etc/qemu/bridge.conf
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
FROM archlinux/archlinux:latest
|
FROM docker.io/archlinux/archlinux:latest
|
||||||
|
|
||||||
RUN systemd-firstboot
|
RUN systemd-firstboot
|
||||||
|
|
||||||
RUN pacman-key --init \
|
RUN pacman-key --init \
|
||||||
&& pacman -Sy --noconfirm archlinux-keyring && pacman -Su --noconfirm \
|
&& pacman -Sy --noconfirm archlinux-keyring && pacman -Su --noconfirm \
|
||||||
&& pacman -S --noconfirm which qemu-full virtiofsd \
|
&& 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 \
|
mtools \
|
||||||
&& pacman -Scc --noconfirm
|
&& pacman -Scc --noconfirm
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.CpuDeleted;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus;
|
import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
|
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.Channel;
|
||||||
import org.jgrapes.core.Component;
|
import org.jgrapes.core.Component;
|
||||||
import org.jgrapes.core.annotation.Handler;
|
import org.jgrapes.core.annotation.Handler;
|
||||||
|
|
@ -64,7 +64,7 @@ public class CpuController extends Component {
|
||||||
*/
|
*/
|
||||||
@Handler
|
@Handler
|
||||||
public void onConfigureQemu(ConfigureQemu event) {
|
public void onConfigureQemu(ConfigureQemu event) {
|
||||||
if (event.state() == State.TERMINATING) {
|
if (event.runState() == RunState.TERMINATING) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Optional.ofNullable(event.configuration().vm.currentCpus)
|
Optional.ofNullable(event.configuration().vm.currentCpus)
|
||||||
|
|
|
||||||
|
|
@ -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.commands.QmpSetPasswordExpiry;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
|
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
|
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.Channel;
|
||||||
import org.jgrapes.core.Component;
|
import org.jgrapes.core.Component;
|
||||||
import org.jgrapes.core.annotation.Handler;
|
import org.jgrapes.core.annotation.Handler;
|
||||||
|
|
@ -67,7 +67,7 @@ public class DisplayController extends Component {
|
||||||
*/
|
*/
|
||||||
@Handler
|
@Handler
|
||||||
public void onConfigureQemu(ConfigureQemu event) {
|
public void onConfigureQemu(ConfigureQemu event) {
|
||||||
if (event.state() == State.TERMINATING) {
|
if (event.runState() == RunState.TERMINATING) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
protocol
|
protocol
|
||||||
|
|
|
||||||
|
|
@ -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.MonitorCommand;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.QmpConfigured;
|
import org.jdrupes.vmoperator.runner.qemu.events.QmpConfigured;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange;
|
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.ExtendedObjectWrapper;
|
||||||
import org.jdrupes.vmoperator.util.FsdUtils;
|
import org.jdrupes.vmoperator.util.FsdUtils;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
|
|
@ -217,7 +217,7 @@ public class Runner extends Component {
|
||||||
private CommandDefinition qemuDefinition;
|
private CommandDefinition qemuDefinition;
|
||||||
private final QemuMonitor qemuMonitor;
|
private final QemuMonitor qemuMonitor;
|
||||||
private Integer resetCounter;
|
private Integer resetCounter;
|
||||||
private State state = State.INITIALIZING;
|
private RunState state = RunState.INITIALIZING;
|
||||||
|
|
||||||
/** Preparatory actions for QEMU start */
|
/** Preparatory actions for QEMU start */
|
||||||
@SuppressWarnings("PMD.FieldNamingConventions")
|
@SuppressWarnings("PMD.FieldNamingConventions")
|
||||||
|
|
@ -467,7 +467,7 @@ public class Runner extends Component {
|
||||||
*/
|
*/
|
||||||
@Handler
|
@Handler
|
||||||
public void onStarted(Started event) {
|
public void onStarted(Started event) {
|
||||||
state = State.STARTING;
|
state = RunState.STARTING;
|
||||||
rep.fire(new RunnerStateChange(state, "RunnerStarted",
|
rep.fire(new RunnerStateChange(state, "RunnerStarted",
|
||||||
"Runner has been started"));
|
"Runner has been started"));
|
||||||
// Start first process(es)
|
// Start first process(es)
|
||||||
|
|
@ -618,9 +618,9 @@ public class Runner extends Component {
|
||||||
*/
|
*/
|
||||||
@Handler(priority = -1000)
|
@Handler(priority = -1000)
|
||||||
public void onConfigureQemuFinal(ConfigureQemu event) {
|
public void onConfigureQemuFinal(ConfigureQemu event) {
|
||||||
if (state == State.STARTING) {
|
if (state == RunState.STARTING) {
|
||||||
fire(new MonitorCommand(new QmpCont()));
|
fire(new MonitorCommand(new QmpCont()));
|
||||||
state = State.RUNNING;
|
state = RunState.RUNNING;
|
||||||
rep.fire(new RunnerStateChange(state, "VmStarted",
|
rep.fire(new RunnerStateChange(state, "VmStarted",
|
||||||
"Qemu has been configured and is continuing"));
|
"Qemu has been configured and is continuing"));
|
||||||
}
|
}
|
||||||
|
|
@ -633,7 +633,7 @@ public class Runner extends Component {
|
||||||
*/
|
*/
|
||||||
@Handler
|
@Handler
|
||||||
public void onConfigureQemu(ConfigureQemu event) {
|
public void onConfigureQemu(ConfigureQemu event) {
|
||||||
if (state == State.RUNNING) {
|
if (state == RunState.RUNNING) {
|
||||||
if (resetCounter != null
|
if (resetCounter != null
|
||||||
&& event.configuration().resetCounter != null
|
&& event.configuration().resetCounter != null
|
||||||
&& event.configuration().resetCounter > resetCounter) {
|
&& event.configuration().resetCounter > resetCounter) {
|
||||||
|
|
@ -659,14 +659,14 @@ public class Runner extends Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// No other process(es) may exit during startup
|
// No other process(es) may exit during startup
|
||||||
if (state == State.STARTING) {
|
if (state == RunState.STARTING) {
|
||||||
logger.severe(() -> "Process " + procDef.name
|
logger.severe(() -> "Process " + procDef.name
|
||||||
+ " has exited with value " + event.exitValue()
|
+ " has exited with value " + event.exitValue()
|
||||||
+ " during startup.");
|
+ " during startup.");
|
||||||
rep.fire(new Stop());
|
rep.fire(new Stop());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (procDef.equals(qemuDefinition) && state == State.RUNNING) {
|
if (procDef.equals(qemuDefinition) && state == RunState.RUNNING) {
|
||||||
rep.fire(new Exit(event.exitValue()));
|
rep.fire(new Exit(event.exitValue()));
|
||||||
}
|
}
|
||||||
logger.info(() -> "Process " + procDef.name
|
logger.info(() -> "Process " + procDef.name
|
||||||
|
|
@ -693,7 +693,7 @@ public class Runner extends Component {
|
||||||
*/
|
*/
|
||||||
@Handler(priority = 10_000)
|
@Handler(priority = 10_000)
|
||||||
public void onStopFirst(Stop event) {
|
public void onStopFirst(Stop event) {
|
||||||
state = State.TERMINATING;
|
state = RunState.TERMINATING;
|
||||||
rep.fire(new RunnerStateChange(state, "VmTerminating",
|
rep.fire(new RunnerStateChange(state, "VmTerminating",
|
||||||
"The VM is being shut down", exitStatus != 0));
|
"The VM is being shut down", exitStatus != 0));
|
||||||
}
|
}
|
||||||
|
|
@ -705,14 +705,14 @@ public class Runner extends Component {
|
||||||
*/
|
*/
|
||||||
@Handler(priority = -10_000)
|
@Handler(priority = -10_000)
|
||||||
public void onStopLast(Stop event) {
|
public void onStopLast(Stop event) {
|
||||||
state = State.STOPPED;
|
state = RunState.STOPPED;
|
||||||
rep.fire(new RunnerStateChange(state, "VmStopped",
|
rep.fire(new RunnerStateChange(state, "VmStopped",
|
||||||
"The VM has been shut down"));
|
"The VM has been shut down"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("PMD.ConfusingArgumentToVarargsMethod")
|
@SuppressWarnings("PMD.ConfusingArgumentToVarargsMethod")
|
||||||
private void shutdown() {
|
private void shutdown() {
|
||||||
if (!Set.of(State.TERMINATING, State.STOPPED).contains(state)) {
|
if (!Set.of(RunState.TERMINATING, RunState.STOPPED).contains(state)) {
|
||||||
fire(new Stop());
|
fire(new Stop());
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -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.Exit;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus;
|
import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus;
|
||||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange;
|
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.runner.qemu.events.ShutdownEvent;
|
||||||
import org.jdrupes.vmoperator.util.GsonPtr;
|
import org.jdrupes.vmoperator.util.GsonPtr;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
|
|
@ -65,8 +65,8 @@ import org.jgrapes.util.events.InitialConfiguration;
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||||
public class StatusUpdater extends Component {
|
public class StatusUpdater extends Component {
|
||||||
|
|
||||||
private static final Set<State> RUNNING_STATES
|
private static final Set<RunState> RUNNING_STATES
|
||||||
= Set.of(State.RUNNING, State.TERMINATING);
|
= Set.of(RunState.RUNNING, RunState.TERMINATING);
|
||||||
|
|
||||||
private String namespace;
|
private String namespace;
|
||||||
private String vmName;
|
private String vmName;
|
||||||
|
|
@ -240,11 +240,11 @@ public class StatusUpdater extends Component {
|
||||||
updateRunningCondition(event, from, cond);
|
updateRunningCondition(event, from, cond);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (event.state() == State.STARTING) {
|
if (event.runState() == RunState.STARTING) {
|
||||||
status.addProperty("ram", GsonPtr.to(from.data())
|
status.addProperty("ram", GsonPtr.to(from.data())
|
||||||
.getAsString("spec", "vm", "maximumRam").orElse("0"));
|
.getAsString("spec", "vm", "maximumRam").orElse("0"));
|
||||||
status.addProperty("cpus", 1);
|
status.addProperty("cpus", 1);
|
||||||
} else if (event.state() == State.STOPPED) {
|
} else if (event.runState() == RunState.STOPPED) {
|
||||||
status.addProperty("ram", "0");
|
status.addProperty("ram", "0");
|
||||||
status.addProperty("cpus", 0);
|
status.addProperty("cpus", 0);
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +252,7 @@ public class StatusUpdater extends Component {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Maybe stop VM
|
// Maybe stop VM
|
||||||
if (event.state() == State.TERMINATING && !event.failed()
|
if (event.runState() == RunState.TERMINATING && !event.failed()
|
||||||
&& guestShutdownStops && shutdownByGuest) {
|
&& guestShutdownStops && shutdownByGuest) {
|
||||||
logger.info(() -> "Stopping VM because of shutdown by guest.");
|
logger.info(() -> "Stopping VM because of shutdown by guest.");
|
||||||
var res = vmStub.patch(V1Patch.PATCH_FORMAT_JSON_PATCH,
|
var res = vmStub.patch(V1Patch.PATCH_FORMAT_JSON_PATCH,
|
||||||
|
|
@ -277,13 +277,13 @@ public class StatusUpdater extends Component {
|
||||||
K8sDynamicModel from, JsonObject cond) {
|
K8sDynamicModel from, JsonObject cond) {
|
||||||
boolean reportedRunning
|
boolean reportedRunning
|
||||||
= "True".equals(cond.get("status").getAsString());
|
= "True".equals(cond.get("status").getAsString());
|
||||||
if (RUNNING_STATES.contains(event.state())
|
if (RUNNING_STATES.contains(event.runState())
|
||||||
&& !reportedRunning) {
|
&& !reportedRunning) {
|
||||||
cond.addProperty("status", "True");
|
cond.addProperty("status", "True");
|
||||||
cond.addProperty("lastTransitionTime",
|
cond.addProperty("lastTransitionTime",
|
||||||
Instant.now().toString());
|
Instant.now().toString());
|
||||||
}
|
}
|
||||||
if (!RUNNING_STATES.contains(event.state())
|
if (!RUNNING_STATES.contains(event.runState())
|
||||||
&& reportedRunning) {
|
&& reportedRunning) {
|
||||||
cond.addProperty("status", "False");
|
cond.addProperty("status", "False");
|
||||||
cond.addProperty("lastTransitionTime",
|
cond.addProperty("lastTransitionTime",
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
package org.jdrupes.vmoperator.runner.qemu.events;
|
package org.jdrupes.vmoperator.runner.qemu.events;
|
||||||
|
|
||||||
import org.jdrupes.vmoperator.runner.qemu.Configuration;
|
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.Channel;
|
||||||
import org.jgrapes.core.Event;
|
import org.jgrapes.core.Event;
|
||||||
|
|
||||||
|
|
@ -34,14 +34,14 @@ import org.jgrapes.core.Event;
|
||||||
public class ConfigureQemu extends Event<Void> {
|
public class ConfigureQemu extends Event<Void> {
|
||||||
|
|
||||||
private final Configuration configuration;
|
private final Configuration configuration;
|
||||||
private final State state;
|
private final RunState state;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new configuration event.
|
* Instantiates a new configuration event.
|
||||||
*
|
*
|
||||||
* @param channels the channels
|
* @param channels the channels
|
||||||
*/
|
*/
|
||||||
public ConfigureQemu(Configuration configuration, State state,
|
public ConfigureQemu(Configuration configuration, RunState state,
|
||||||
Channel... channels) {
|
Channel... channels) {
|
||||||
super(channels);
|
super(channels);
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
|
@ -62,7 +62,7 @@ public class ConfigureQemu extends Event<Void> {
|
||||||
*
|
*
|
||||||
* @return the state
|
* @return the state
|
||||||
*/
|
*/
|
||||||
public State state() {
|
public RunState runState() {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,11 @@ public class RunnerStateChange extends Event<Void> {
|
||||||
/**
|
/**
|
||||||
* The state.
|
* The state.
|
||||||
*/
|
*/
|
||||||
public enum State {
|
public enum RunState {
|
||||||
INITIALIZING, STARTING, RUNNING, TERMINATING, STOPPED
|
INITIALIZING, STARTING, RUNNING, TERMINATING, STOPPED
|
||||||
}
|
}
|
||||||
|
|
||||||
private final State state;
|
private final RunState state;
|
||||||
private final String reason;
|
private final String reason;
|
||||||
private final String message;
|
private final String message;
|
||||||
private final boolean failed;
|
private final boolean failed;
|
||||||
|
|
@ -48,7 +48,7 @@ public class RunnerStateChange extends Event<Void> {
|
||||||
* @param message the message
|
* @param message the message
|
||||||
* @param channels the channels
|
* @param channels the channels
|
||||||
*/
|
*/
|
||||||
public RunnerStateChange(State state, String reason, String message,
|
public RunnerStateChange(RunState state, String reason, String message,
|
||||||
Channel... channels) {
|
Channel... channels) {
|
||||||
this(state, reason, message, false, channels);
|
this(state, reason, message, false, channels);
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +62,7 @@ public class RunnerStateChange extends Event<Void> {
|
||||||
* @param failed the failed
|
* @param failed the failed
|
||||||
* @param channels the channels
|
* @param channels the channels
|
||||||
*/
|
*/
|
||||||
public RunnerStateChange(State state, String reason, String message,
|
public RunnerStateChange(RunState state, String reason, String message,
|
||||||
boolean failed, Channel... channels) {
|
boolean failed, Channel... channels) {
|
||||||
super(channels);
|
super(channels);
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
|
@ -76,7 +76,7 @@ public class RunnerStateChange extends Event<Void> {
|
||||||
*
|
*
|
||||||
* @return the state
|
* @return the state
|
||||||
*/
|
*/
|
||||||
public State state() {
|
public RunState runState() {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ public class TrayMovedEvent extends MonitorEvent {
|
||||||
*
|
*
|
||||||
* @return the tray state
|
* @return the tray state
|
||||||
*/
|
*/
|
||||||
public TrayState state() {
|
public TrayState trayState() {
|
||||||
return data().get("tray-open").asBoolean()
|
return data().get("tray-open").asBoolean()
|
||||||
? TrayState.OPEN
|
? TrayState.OPEN
|
||||||
: TrayState.CLOSED;
|
: TrayState.CLOSED;
|
||||||
|
|
|
||||||
3158
package-lock.json
generated
|
|
@ -21,6 +21,10 @@
|
||||||
"typedoc-plugin-missing-exports": "^2.1.0",
|
"typedoc-plugin-missing-exports": "^2.1.0",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.2.2"
|
||||||
},
|
},
|
||||||
|
"overrides": {
|
||||||
|
"node-gyp": "^10.1.0",
|
||||||
|
"glob": "^9.0.0"
|
||||||
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
"extends": "eslint:recommended",
|
"extends": "eslint:recommended",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
FROM alpine:3.19
|
FROM docker.io/alpine:3.19
|
||||||
|
|
||||||
RUN apk update &&\
|
RUN apk update &&\
|
||||||
apk add --no-cache inotify-tools &&\
|
apk add --no-cache inotify-tools &&\
|
||||||
|
|
|
||||||
4
webpages/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
_site
|
||||||
|
Gemfile.lock
|
||||||
|
.bundle
|
||||||
|
.jekyll-cache
|
||||||
5
webpages/Gemfile
Normal file
|
|
@ -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'
|
||||||
10
webpages/_config.yml
Normal file
|
|
@ -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.
|
||||||
23
webpages/_includes/matomo.html
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<!-- Matomo anonymous, no cookies (https://matomo.org/blog/2018/04/how-to-not-process-any-personal-data-with-matomo-and-what-it-means-for-you/) -->
|
||||||
|
<script type="text/javascript">
|
||||||
|
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(['disableCookies']);
|
||||||
|
_paq.push(['trackPageView']);
|
||||||
|
_paq.push(['enableLinkTracking']);
|
||||||
|
(function() {
|
||||||
|
var u="//piwik.mnl.de/";
|
||||||
|
_paq.push(['setTrackerUrl', u+'piwik.php']);
|
||||||
|
_paq.push(['setSiteId', '14']);
|
||||||
|
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);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<noscript>
|
||||||
|
<img src="https://piwik.mnl.de/piwik.php?idsite=14&rec=1&action_name=JGrapes" style="border:0" alt="" />
|
||||||
|
</noscript>
|
||||||
|
<!-- End Matomo Code -->
|
||||||
|
|
||||||
96
webpages/_includes/toc.html
Normal file
|
|
@ -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: '<h' %}
|
||||||
|
{% assign firstHeader = true %}
|
||||||
|
|
||||||
|
{% capture listModifier %}{% if orderedList %}1.{% else %}-{% endif %}{% endcapture %}
|
||||||
|
|
||||||
|
{% for node in nodes %}
|
||||||
|
{% if node == "" %}
|
||||||
|
{% continue %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% assign headerLevel = node | replace: '"', '' | slice: 0, 1 | times: 1 %}
|
||||||
|
|
||||||
|
{% if headerLevel < minHeader or headerLevel > maxHeader %}
|
||||||
|
{% continue %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if firstHeader %}
|
||||||
|
{% assign firstHeader = false %}
|
||||||
|
{% assign minHeader = headerLevel %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% assign indentAmount = headerLevel | minus: minHeader %}
|
||||||
|
{% assign _workspace = node | split: '</h' %}
|
||||||
|
|
||||||
|
{% assign _idWorkspace = _workspace[0] | split: 'id="' %}
|
||||||
|
{% assign _idWorkspace = _idWorkspace[1] | split: '"' %}
|
||||||
|
{% assign html_id = _idWorkspace[0] %}
|
||||||
|
|
||||||
|
{% assign _classWorkspace = _workspace[0] | split: 'class="' %}
|
||||||
|
{% assign _classWorkspace = _classWorkspace[1] | split: '"' %}
|
||||||
|
{% assign html_class = _classWorkspace[0] %}
|
||||||
|
|
||||||
|
{% if html_class contains "no_toc" %}
|
||||||
|
{% continue %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% capture _hAttrToStrip %}{{ _workspace[0] | 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 }}
|
||||||
71
webpages/_layouts/vm-operator.html
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="chrome=1">
|
||||||
|
<meta name="referrer" content="no-referrer">
|
||||||
|
<link rel="icon" type="image/svg+xml" href="favicon.svg" sizes="any">
|
||||||
|
<link rel="stylesheet" href="../stylesheets/styles.css">
|
||||||
|
<link rel="stylesheet" href="../stylesheets/pygment_trac.css">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<!--[if lt IE 9]>
|
||||||
|
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||||
|
<![endif]-->
|
||||||
|
{% seo %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="wrapper">
|
||||||
|
<header>
|
||||||
|
<div>
|
||||||
|
<div style="float: left;">
|
||||||
|
<h1><a style="color: #222;" href="http://vm-operator.jdrupes.org">VM-Operator</a></h1>
|
||||||
|
<h3>By <a href="https://github.com/mnlipp">Michael N. Lipp</a></h3>
|
||||||
|
<p><a rel="me" href="https://fosstodon.org/@mnl"><img alt="Mastodon Follow"
|
||||||
|
src="https://img.shields.io/mastodon/follow/108843609567976408?domain=https%3A%2F%2Ffosstodon.org&style=social"></a></p>
|
||||||
|
</div>
|
||||||
|
<div style="float: right; width: 7em;">
|
||||||
|
<img alt="VM-Operator Logo" src="VM-Operator.svg">
|
||||||
|
</div>
|
||||||
|
<div style="clear:both;"></div>
|
||||||
|
</div>
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<p class="view"><a href="https://github.com/mnlipp/VM-Operator">View GitHub Project</a></p>
|
||||||
|
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<p class="part-entry"><a href="index.html">Overview</a></p>
|
||||||
|
<p class="part-entry"><a href="runner.html">The Runner</a></p>
|
||||||
|
<p class="part-list-title"><a href="manager.html">The Manager</a></p>
|
||||||
|
<ul style="margin-bottom: 0;" class="no-bullets">
|
||||||
|
<li><p class="part-entry"><a href="controller.html">The Controller</a></p></li>
|
||||||
|
</ul>
|
||||||
|
<p class="part-list-title"><a href="webgui.html">The Web-GUI</a></p>
|
||||||
|
<ul style="margin-bottom: 0;" class="no-bullets">
|
||||||
|
<li><p class="part-entry"><a href="admin-gui.html">For Admins</a></p></li>
|
||||||
|
<li><p class="part-entry"><a href="user-gui.html">For Users</a></p></li>
|
||||||
|
</ulstyle="margin-bottom: 0;">
|
||||||
|
<p class="part-list-title"><a href="upgrading.html">Upgrading</a></p>
|
||||||
|
<p class="part-list-title"><a href="latest-release/javadoc/index.html">Javadoc</a></p>
|
||||||
|
|
||||||
|
</header>
|
||||||
|
<section>
|
||||||
|
<div class="post-date"><span class="post-meta">{{ page.date | date: "%b %-d, %Y" }}</span></div>
|
||||||
|
{% if page.tocTitle %}
|
||||||
|
<h1>{{ page.tocTitle }}</h1>
|
||||||
|
{% include toc.html html=content %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{{ content }}
|
||||||
|
</section>
|
||||||
|
<footer>
|
||||||
|
<p><small>Hosted on GitHub Pages — <a href="https://github.com/site/terms">Terms</a>
|
||||||
|
— <a href="https://github.com/site/privacy">Privacy</a>
|
||||||
|
— Theme derived from <a href="https://github.com/orderedlist/minimal">minimal</a></small></p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% include matomo.html %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
69
webpages/stylesheets/pygment_trac.css
Normal file
|
|
@ -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 }
|
||||||
244
webpages/stylesheets/styles.css
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
webpages/vm-operator/02_2_operator.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
webpages/vm-operator/VM-Operator-GUI-preview.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
webpages/vm-operator/VM-Operator-GUI-view.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
173
webpages/vm-operator/VM-Operator-with-font.svg
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="93.557968mm"
|
||||||
|
height="91.220795mm"
|
||||||
|
viewBox="0 0 331.50461 323.22329"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
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/">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.7"
|
||||||
|
inkscape:cx="293.57143"
|
||||||
|
inkscape:cy="145.71429"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
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" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Ebene 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(799.83239,410.74206)">
|
||||||
|
<g
|
||||||
|
id="path300"
|
||||||
|
inkscape:transform-center-x="-0.49891951"
|
||||||
|
inkscape:transform-center-y="-10.814906"
|
||||||
|
transform="matrix(0.93749998,0,0,0.93749998,-364.15225,128.12438)">
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="fill:#326de6;fill-opacity:1;stroke:#ffffff;stroke-linecap:square;stroke-miterlimit:0;paint-order:fill markers stroke"
|
||||||
|
id="path1033"
|
||||||
|
inkscape:flatsided="false"
|
||||||
|
sodipodi:sides="7"
|
||||||
|
sodipodi:cx="-790.008"
|
||||||
|
sodipodi:cy="-357.15076"
|
||||||
|
sodipodi:r1="221.23064"
|
||||||
|
sodipodi:r2="199.10757"
|
||||||
|
sodipodi:arg1="1.1215879"
|
||||||
|
sodipodi:arg2="1.5605315"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m -693.93801,-157.86816 -94.02622,-0.18551 -97.95052,0.26412 -58.47935,-73.62832 -61.2776,-76.41613 21.10362,-91.6275 21.53854,-95.55347 84.79518,-40.6293 88.13578,-42.7371 84.6342,40.96358 88.36497,42.26117 20.74194,91.71007 22.05354,95.43592 -58.76943,73.39699 z"
|
||||||
|
transform="matrix(0.81788201,0,0,0.81788201,358.19384,-101.37507)"
|
||||||
|
inkscape:transform-center-x="1.2804791"
|
||||||
|
inkscape:transform-center-y="-8.9686433" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:16.6665px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';letter-spacing:0px;word-spacing:0px;fill:none;stroke:#ffffff;stroke-width:1.04165;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
x="-753.07837"
|
||||||
|
y="-183.35805"
|
||||||
|
id="text300"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan298"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:semi-condensed;font-size:199.997px;font-family:'Nimbus Sans Narrow';-inkscape-font-specification:'Nimbus Sans Narrow, Bold Semi-Condensed';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ff6600;fill-opacity:1;stroke:#ffffff;stroke-width:1.04165;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
x="-753.07837"
|
||||||
|
y="-183.35805">VM</tspan></text>
|
||||||
|
<g
|
||||||
|
id="g1773"
|
||||||
|
transform="matrix(1.5551014,0,0,1.5551014,-923.85519,-409.37793)"
|
||||||
|
style="stroke:#ffffff;stroke-opacity:1">
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:0.692134;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136"
|
||||||
|
cx="149.36122"
|
||||||
|
cy="159.37894"
|
||||||
|
r="5.9195638" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:0.692134;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-8"
|
||||||
|
cx="162.51581"
|
||||||
|
cy="159.37894"
|
||||||
|
r="5.9195638" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:0.692134;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-1"
|
||||||
|
cx="175.67039"
|
||||||
|
cy="159.37894"
|
||||||
|
r="5.9195638" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:0.692134;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-2"
|
||||||
|
cx="188.82498"
|
||||||
|
cy="159.37894"
|
||||||
|
r="5.9195638" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:0.692134;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-8-8"
|
||||||
|
cx="155.7412"
|
||||||
|
cy="170.6261"
|
||||||
|
r="5.9195638" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:0.692134;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-1-9"
|
||||||
|
cx="168.89577"
|
||||||
|
cy="170.6261"
|
||||||
|
r="5.9195638" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:0.692134;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-2-3"
|
||||||
|
cx="182.05035"
|
||||||
|
cy="170.6261"
|
||||||
|
r="5.9195638" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:0.692134;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-8-6"
|
||||||
|
cx="162.25272"
|
||||||
|
cy="182.0706"
|
||||||
|
r="5.9195638" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:0.692134;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-1-8"
|
||||||
|
cx="175.4073"
|
||||||
|
cy="182.0706"
|
||||||
|
r="5.9195638" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:0.692134;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-2-0"
|
||||||
|
cx="168.63269"
|
||||||
|
cy="193.12045"
|
||||||
|
r="5.9195638" />
|
||||||
|
<g
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:180px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial Bold';letter-spacing:0px;word-spacing:0px;fill:#238220;fill-opacity:1;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
id="flowRoot4814"
|
||||||
|
transform="matrix(0.16182744,0,0,0.16070376,137.53832,122.72639)">
|
||||||
|
<path
|
||||||
|
d="m 210.10352,57.161102 h 25.92773 V 138.7236 q 0,15.9961 -2.8125,24.60938 -3.7793,11.25 -13.71094,18.10547 -9.93164,6.76757 -26.1914,6.76757 -19.07227,0 -29.35547,-10.63476 -10.28321,-10.72266 -10.3711,-31.37696 l 24.52149,-2.8125 q 0.43945,11.07422 3.25195,15.64454 4.21875,6.94336 12.83203,6.94336 8.70117,0 12.30469,-4.92188 3.60352,-5.00977 3.60352,-20.6543 z"
|
||||||
|
style="fill:#238220;fill-opacity:1;stroke:#ffffff;stroke-opacity:1"
|
||||||
|
id="path4823"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 7.5 KiB |
184
webpages/vm-operator/VM-Operator.svg
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="93.557968mm"
|
||||||
|
height="91.220795mm"
|
||||||
|
viewBox="0 0 331.50461 323.22329"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
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/">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.7"
|
||||||
|
inkscape:cx="245"
|
||||||
|
inkscape:cy="145.71429"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
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" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Ebene 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(799.83239,410.74206)">
|
||||||
|
<g
|
||||||
|
id="path300"
|
||||||
|
inkscape:transform-center-x="-0.49891951"
|
||||||
|
inkscape:transform-center-y="-10.814906"
|
||||||
|
transform="matrix(0.93749998,0,0,0.93749998,-364.15225,128.12438)">
|
||||||
|
<path
|
||||||
|
sodipodi:type="star"
|
||||||
|
style="fill:#326de6;fill-opacity:1;stroke:#ffffff;stroke-linecap:square;stroke-miterlimit:0;paint-order:fill markers stroke"
|
||||||
|
id="path1033"
|
||||||
|
inkscape:flatsided="false"
|
||||||
|
sodipodi:sides="7"
|
||||||
|
sodipodi:cx="-790.008"
|
||||||
|
sodipodi:cy="-357.15076"
|
||||||
|
sodipodi:r1="221.23064"
|
||||||
|
sodipodi:r2="199.10757"
|
||||||
|
sodipodi:arg1="1.1215879"
|
||||||
|
sodipodi:arg2="1.5605315"
|
||||||
|
inkscape:rounded="0"
|
||||||
|
inkscape:randomized="0"
|
||||||
|
d="m -693.93801,-157.86816 -94.02622,-0.18551 -97.95052,0.26412 -58.47935,-73.62832 -61.2776,-76.41613 21.10362,-91.6275 21.53854,-95.55347 84.79518,-40.6293 88.13578,-42.7371 84.6342,40.96358 88.36497,42.26117 20.74194,91.71007 22.05354,95.43592 -58.76943,73.39699 z"
|
||||||
|
transform="matrix(0.81788201,0,0,0.81788201,358.19384,-101.37507)"
|
||||||
|
inkscape:transform-center-x="1.2804791"
|
||||||
|
inkscape:transform-center-y="-8.9686433" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
aria-label="VM"
|
||||||
|
id="text300"
|
||||||
|
style="font-size:16.6665px;-inkscape-font-specification:'sans-serif, Normal';letter-spacing:0px;word-spacing:0px;fill:none;stroke:#ffffff;stroke-width:1.04165">
|
||||||
|
<g
|
||||||
|
id="path305">
|
||||||
|
<path
|
||||||
|
style="color:#000000;-inkscape-font-specification:'Nimbus Sans Narrow, Bold Semi-Condensed';fill:#ff6600;stroke:none;-inkscape-stroke:none"
|
||||||
|
d="m -698.0792,-217.35754 -25.39961,-109.59835 h -26.39961 l 39.59941,143.59784 h 23.39965 l 39.99939,-143.59784 h -25.59961 z"
|
||||||
|
id="path312" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;-inkscape-font-specification:'Nimbus Sans Narrow, Bold Semi-Condensed';fill:#ffffff;stroke:none;-inkscape-stroke:none"
|
||||||
|
d="m -750.5625,-327.47656 39.88672,144.63867 h 24.19141 l 40.29101,-144.63867 h -26.69922 l -25.18554,107.82226 -24.98633,-107.82226 z m 1.36719,1.04101 h 25.30273 l 25.30664,109.19532 1.01367,0.002 25.50586,-109.19727 h 24.50196 l -39.71094,142.55664 h -22.60742 z"
|
||||||
|
id="path325" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="path307">
|
||||||
|
<path
|
||||||
|
style="color:#000000;-inkscape-font-specification:'Nimbus Sans Narrow, Bold Semi-Condensed';fill:#ff6600;stroke:none;-inkscape-stroke:none"
|
||||||
|
d="m -518.28172,-326.95589 h -35.39947 l -21.19968,113.99829 -21.59968,-113.99829 h -35.79946 v 143.59784 h 22.79966 v -121.79817 l 21.79967,121.79817 h 24.19963 l 22.39967,-121.79817 v 121.79817 h 22.79966 z"
|
||||||
|
id="path318" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;-inkscape-font-specification:'Nimbus Sans Narrow, Bold Semi-Condensed';fill:#ffffff;stroke:none;-inkscape-stroke:none"
|
||||||
|
d="m -632.80078,-327.47656 v 144.63867 h 23.8418 v -116.45313 l 20.84179,116.45313 h 25.07032 l 21.44531,-116.60742 v 116.60742 h 23.83984 v -144.63867 h -0.51953 -35.83203 l -20.77149,111.69726 -21.16406,-111.69726 z m 1.04101,1.04101 h 34.84766 l 21.51953,113.57422 1.02344,-0.002 21.12109,-113.57227 h 34.44532 v 142.55664 h -21.75782 v -121.27734 l -1.0332,-0.0937 -22.32031,121.37109 h -23.33008 l -21.72266,-121.36914 -1.03515,0.0918 v 121.27734 h -21.75782 z"
|
||||||
|
id="path320" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:1.07634;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136"
|
||||||
|
cx="-691.58337"
|
||||||
|
cy="-161.52753"
|
||||||
|
r="9.2055216" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:1.07634;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-8"
|
||||||
|
cx="-671.12665"
|
||||||
|
cy="-161.52753"
|
||||||
|
r="9.2055216" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:1.07634;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-1"
|
||||||
|
cx="-650.66992"
|
||||||
|
cy="-161.52753"
|
||||||
|
r="9.2055216" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:1.07634;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-2"
|
||||||
|
cx="-630.2132"
|
||||||
|
cy="-161.52753"
|
||||||
|
r="9.2055216" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:1.07634;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-8-8"
|
||||||
|
cx="-681.66187"
|
||||||
|
cy="-144.03705"
|
||||||
|
r="9.2055216" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:1.07634;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-1-9"
|
||||||
|
cx="-661.20514"
|
||||||
|
cy="-144.03705"
|
||||||
|
r="9.2055216" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:1.07634;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-2-3"
|
||||||
|
cx="-640.74841"
|
||||||
|
cy="-144.03705"
|
||||||
|
r="9.2055216" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:1.07634;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-8-6"
|
||||||
|
cx="-671.53577"
|
||||||
|
cy="-126.23969"
|
||||||
|
r="9.2055216" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:1.07634;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-1-8"
|
||||||
|
cx="-651.07904"
|
||||||
|
cy="-126.23969"
|
||||||
|
r="9.2055216" />
|
||||||
|
<circle
|
||||||
|
style="fill:#6606b5;fill-opacity:1;stroke:#ffffff;stroke-width:1.07634;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path4136-2-0"
|
||||||
|
cx="-661.61426"
|
||||||
|
cy="-109.05605"
|
||||||
|
r="9.2055216" />
|
||||||
|
<g
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:180px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial Bold';letter-spacing:0px;word-spacing:0px;fill:#238220;fill-opacity:1;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
id="flowRoot4814"
|
||||||
|
transform="matrix(0.25165808,0,0,0.24991064,-709.96916,-218.52595)">
|
||||||
|
<path
|
||||||
|
d="m 210.10352,57.161102 h 25.92773 V 138.7236 q 0,15.9961 -2.8125,24.60938 -3.7793,11.25 -13.71094,18.10547 -9.93164,6.76757 -26.1914,6.76757 -19.07227,0 -29.35547,-10.63476 -10.28321,-10.72266 -10.3711,-31.37696 l 24.52149,-2.8125 q 0.43945,11.07422 3.25195,15.64454 4.21875,6.94336 12.83203,6.94336 8.70117,0 12.30469,-4.92188 3.60352,-5.00977 3.60352,-20.6543 z"
|
||||||
|
style="fill:#238220;fill-opacity:1;stroke:#ffffff;stroke-opacity:1"
|
||||||
|
id="path4823"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.5 KiB |
BIN
webpages/vm-operator/VmViewer-preview.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
18
webpages/vm-operator/admin-gui.md
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
---
|
||||||
|
title: VM-Operator Web-GUI for Admins
|
||||||
|
layout: vm-operator
|
||||||
|
---
|
||||||
|
|
||||||
|
# Administrator view
|
||||||
|
|
||||||
|
An overview display shows the current CPU and RAM usage and a graph
|
||||||
|
with recent changes.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The detail display lists all VMs. From here you can start and stop
|
||||||
|
the VMs and adjust the CPU and RAM usages (modifies the definition
|
||||||
|
in kubernetes).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
226
webpages/vm-operator/controller.md
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
---
|
||||||
|
title: VM-Operator Controller
|
||||||
|
layout: vm-operator
|
||||||
|
---
|
||||||
|
|
||||||
|
# The Controller
|
||||||
|
|
||||||
|
The controller component (which is part of the manager) monitors
|
||||||
|
custom resources of kind `VirtualMachine`. It creates or modifies
|
||||||
|
other resources in the cluster as required to get the VM defined
|
||||||
|
by the CR up and running.
|
||||||
|
|
||||||
|
Here is the sample definition of a VM from the
|
||||||
|
["local-path" example](https://github.com/mnlipp/VM-Operator/tree/main/example/local-path):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
apiVersion: "vmoperator.jdrupes.org/v1"
|
||||||
|
kind: VirtualMachine
|
||||||
|
metadata:
|
||||||
|
namespace: vmop-demo
|
||||||
|
name: test-vm
|
||||||
|
spec:
|
||||||
|
guestShutdownStops: false
|
||||||
|
|
||||||
|
vm:
|
||||||
|
state: Running
|
||||||
|
maximumCpus: 4
|
||||||
|
currentCpus: 2
|
||||||
|
maximumRam: 8Gi
|
||||||
|
currentRam: 4Gi
|
||||||
|
|
||||||
|
networks:
|
||||||
|
- user: {}
|
||||||
|
|
||||||
|
disks:
|
||||||
|
- volumeClaimTemplate:
|
||||||
|
metadata:
|
||||||
|
name: system
|
||||||
|
spec:
|
||||||
|
storageClassName: ""
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: vmrunner
|
||||||
|
app.kubernetes.io/instance: test-vm
|
||||||
|
vmrunner.jdrupes.org/disk: system
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 40Gi
|
||||||
|
- 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: 5910
|
||||||
|
# Since 3.0.0:
|
||||||
|
# generateSecret: false
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pod management
|
||||||
|
|
||||||
|
The central resource created by the controller is a
|
||||||
|
[stateful set](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/)
|
||||||
|
with the same name as the VM (metadata.name). Its number of replicas is
|
||||||
|
set to 1 if `spec.vm.state` is "Running" (default is "Stopped" which sets
|
||||||
|
replicas to 0).
|
||||||
|
|
||||||
|
Property `spec.guestShutdownStops` (since 2.2.0) controls the effect of a
|
||||||
|
shutdown initiated by the guest. If set to `false` (default) a new pod
|
||||||
|
is automatically created by the stateful set controller and the VM thus
|
||||||
|
restarted. If set to `true`, the runner sets `spec.vm.state` to "Stopped"
|
||||||
|
before terminating and by this prevents the creation of a new pod.
|
||||||
|
|
||||||
|
## Defining the basics
|
||||||
|
|
||||||
|
How to define the number of CPUs and the size of the RAM of the VM
|
||||||
|
should be obvious from the example. Note that changes of the current
|
||||||
|
number of CPUs and the current RAM size will be propagated to
|
||||||
|
running VMs.
|
||||||
|
|
||||||
|
## Defining disks
|
||||||
|
|
||||||
|
Maybe the most interesting part is the definition of the VM's disks.
|
||||||
|
This is done by adding one or more `volumeClaimTemplate`s to the
|
||||||
|
list of disks. As its name suggests, such a template is used by the
|
||||||
|
controller to generate a PVC.
|
||||||
|
|
||||||
|
The example template does not define any storage. Rather it references
|
||||||
|
some PV that you must have created first. This may be your first approach
|
||||||
|
if you have existing storage from running the VM outside Kubernetes
|
||||||
|
(e.g. with libvirtd).
|
||||||
|
|
||||||
|
If you have ceph or some other full fledged storage provider installed
|
||||||
|
and create a new VM, provisioning a disk can happen automatically
|
||||||
|
as shown in this example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
disks:
|
||||||
|
- volumeClaimTemplate:
|
||||||
|
metadata:
|
||||||
|
name: system
|
||||||
|
spec:
|
||||||
|
storageClassName: rook-ceph-block
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 40Gi
|
||||||
|
```
|
||||||
|
|
||||||
|
The disk will be available as "/dev/*name*-disk" in the VM,
|
||||||
|
using the string from `.volumeClaimTemplate.metadata.name` as *name*.
|
||||||
|
If no name is defined in the metadata, then "/dev/disk-*n*"
|
||||||
|
is used instead, with *n* being the index of the disk
|
||||||
|
definition in the list of disks.
|
||||||
|
|
||||||
|
Apart from appending "-disk" to the name (or generating the name) the
|
||||||
|
`volumeClaimTemplate` is simply copied into the stateful set definition
|
||||||
|
for the VM (with some additional labels, see below). The controller
|
||||||
|
for stateful sets appends the started pod's name to the name of the
|
||||||
|
volume claim templates when it creates the PVCs. Therefore you'll
|
||||||
|
eventually find the PVCs as "*name*-disk-*vmName*-0"
|
||||||
|
(or "disk-*n*-*vmName*-0").
|
||||||
|
|
||||||
|
PVCs generated from stateful set definitions are considered "precious"
|
||||||
|
and never removed automatically. This behavior fits perfectly for VMs.
|
||||||
|
Usually, you do not want the disks to be removed automatically when
|
||||||
|
you (maybe accidentally) remove the CR for the VM. To simplify the lookup
|
||||||
|
for an eventual (manual) removal, all PVCs are labeled with
|
||||||
|
"app.kubernetes.io/name: vm-runner", "app.kubernetes.io/instance: *vmName*",
|
||||||
|
and "app.kubernetes.io/managed-by: vm-operator".
|
||||||
|
|
||||||
|
## Choosing an image for the runner
|
||||||
|
|
||||||
|
The image used for the runner can be configured with
|
||||||
|
[`spec.image`](https://github.com/mnlipp/VM-Operator/blob/7e094e720b7b59a5e50f4a9a4ad29a6000ec76e6/deploy/crds/vms-crd.yaml#L19).
|
||||||
|
This is a mapping with either a single key `source` or a detailed
|
||||||
|
configuration using the keys `repository`, `path` etc.
|
||||||
|
|
||||||
|
Currently two runner images are maintained. One that is based on
|
||||||
|
Arch Linux (`ghcr.io/mnlipp/org.jdrupes.vmoperator.runner.qemu-arch`) and a
|
||||||
|
second one based on Alpine (`ghcr.io/mnlipp/org.jdrupes.vmoperator.runner.qemu-alpine`).
|
||||||
|
|
||||||
|
Starting with release 1.0, all versions of runner images and managers
|
||||||
|
that have the same major release number are guaranteed to be compatible.
|
||||||
|
|
||||||
|
## Generating cloud-init data
|
||||||
|
|
||||||
|
*Since: 2.2.0*
|
||||||
|
|
||||||
|
The optional object `.spec.cloudInit` with sub-objects `.cloudInit.metaData`,
|
||||||
|
`.cloudInit.userData` and `.cloudInit.networkConfig` can be used to provide
|
||||||
|
data for
|
||||||
|
[cloud-init](https://cloudinit.readthedocs.io/en/latest/index.html).
|
||||||
|
The data from the CRD will be made available to the VM by the runner
|
||||||
|
as a vfat formatted disk (see the description of
|
||||||
|
[NoCloud](https://cloudinit.readthedocs.io/en/latest/reference/datasources/nocloud.html)).
|
||||||
|
|
||||||
|
If `.metaData.instance-id` is not defined, the controller automatically
|
||||||
|
generates it from the CRD's `resourceVersion`. If `.metaData.local-hostname`
|
||||||
|
is not defined, the controller adds this property using the value from
|
||||||
|
`metadata.name`.
|
||||||
|
|
||||||
|
Note that there are no schema definitions available for `.userData`
|
||||||
|
and `.networkConfig`. Whatever is defined in the CRD is copied to
|
||||||
|
the corresponding cloud-init file without any checks. (The introductory
|
||||||
|
comment `#cloud-config` required at the beginning of `.userData` is
|
||||||
|
generated automatically by the runner.)
|
||||||
|
|
||||||
|
## Display secret/password
|
||||||
|
|
||||||
|
*Since: 2.3.0*
|
||||||
|
|
||||||
|
You can define a display password using a Kubernetes secret.
|
||||||
|
When you start a VM, the controller checks if there is a secret
|
||||||
|
with labels "app.kubernetes.io/name: vm-runner,
|
||||||
|
app.kubernetes.io/component: display-secret,
|
||||||
|
app.kubernetes.io/instance: *vmname*" in the namespace of the
|
||||||
|
VM definition. The name of the secret can be chosen freely.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
kind: Secret
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: test-vm-display-secret
|
||||||
|
namespace: vmop-demo
|
||||||
|
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==
|
||||||
|
# Since 3.0.0:
|
||||||
|
# password-expiry: bmV2ZXI=
|
||||||
|
```
|
||||||
|
|
||||||
|
If such a secret for the VM is found, the VM is configured to use
|
||||||
|
the display password specified. The display password in the secret
|
||||||
|
can be updated while the VM runs[^delay]. Activating/deactivating
|
||||||
|
the display password while a VM runs is not supported by Qemu and
|
||||||
|
therefore requires stopping the VM, adding/removing the secret and
|
||||||
|
restarting the VM.
|
||||||
|
|
||||||
|
[^delay]: Be aware of the possible delay, see e.g.
|
||||||
|
[here](https://web.archive.org/web/20240223073838/https://ahmet.im/blog/kubernetes-secret-volumes-delay/).
|
||||||
|
|
||||||
|
*Since: 3.0.0*
|
||||||
|
|
||||||
|
The secret's `data` can have an additional property `data.password-expiry` which
|
||||||
|
specifies a (base64 encoded) expiry date for the password. Supported
|
||||||
|
values are those defined by qemu (`+n` seconds from now, `n` Unix
|
||||||
|
timestamp, `never` and `now`).
|
||||||
|
|
||||||
|
Unless `spec.vm.display.spice.generateSecret` is set to `false` in the VM
|
||||||
|
definition (CRD), the controller creates a secret for the display
|
||||||
|
password automatically if none is found. The secret is created
|
||||||
|
with a random password that expires immediately, which makes the
|
||||||
|
display effectively inaccessible until the secret is modified.
|
||||||
|
Note that a password set manually may be overwritten by components
|
||||||
|
of the manager unless the password-expiry is set to "never" or
|
||||||
|
some time in the future.
|
||||||
|
|
||||||
|
## Further reading
|
||||||
|
|
||||||
|
For a detailed description of the available configuration options see the
|
||||||
|
[CRD](https://github.com/mnlipp/VM-Operator/blob/main/deploy/crds/vms-crd.yaml).
|
||||||
88
webpages/vm-operator/favicon.svg
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="10.64148mm"
|
||||||
|
height="12.555316mm"
|
||||||
|
viewBox="0 0 37.706033 44.487341"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.91 r13725"
|
||||||
|
sodipodi:docname="ML-Logo1.svg"
|
||||||
|
inkscape:export-filename="/home/mnl/Dokumente/mnl/ML-Logo1.png"
|
||||||
|
inkscape:export-xdpi="299.41104"
|
||||||
|
inkscape:export-ydpi="299.41104">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.7"
|
||||||
|
inkscape:cx="132.46074"
|
||||||
|
inkscape:cy="-297.07411"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
fit-margin-top="1"
|
||||||
|
fit-margin-left="1"
|
||||||
|
fit-margin-right="1"
|
||||||
|
fit-margin-bottom="1"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1016"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Ebene 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-175.34341,-117.71255)">
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="187.14285"
|
||||||
|
y="150.93362"
|
||||||
|
id="text3370"
|
||||||
|
sodipodi:linespacing="125%"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan3372"
|
||||||
|
x="187.14285"
|
||||||
|
y="150.93362"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono'">M</tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-style:normal;font-weight:normal;font-size:51.30387497px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
x="173.50081"
|
||||||
|
y="158.65659"
|
||||||
|
id="text3370-6"
|
||||||
|
sodipodi:linespacing="125%"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan3372-5"
|
||||||
|
x="173.50081"
|
||||||
|
y="158.65659"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono'">L</tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
60
webpages/vm-operator/index.md
Normal file
|
|
@ -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.
|
||||||
150
webpages/vm-operator/manager.md
Normal file
|
|
@ -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`.
|
||||||
108
webpages/vm-operator/runner.md
Normal file
|
|
@ -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.
|
||||||
29
webpages/vm-operator/upgrading.md
Normal file
|
|
@ -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.
|
||||||
143
webpages/vm-operator/user-gui.md
Normal file
|
|
@ -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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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.)
|
||||||
117
webpages/vm-operator/webgui.md
Normal file
|
|
@ -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.
|
||||||