Merge branch 'release/v3.1.x' into release/v3.x
6
.github/workflows/gradle.yml
vendored
|
|
@ -22,10 +22,10 @@ jobs:
|
|||
fetch-depth: 0
|
||||
- name: Install graphviz
|
||||
run: sudo apt-get install graphviz
|
||||
- name: Set up JDK 17
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew -Prepo.access.token=${{ secrets.REPO_ACCESS_TOKEN }} stage
|
||||
run: ./gradlew -Pwebsite.push.token=${{ secrets.WEBSITE_PUSH_TOKEN }} stage
|
||||
|
|
|
|||
6
.github/workflows/release.yml
vendored
|
|
@ -31,10 +31,10 @@ jobs:
|
|||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up JDK 17
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
- name: Push with Gradle
|
||||
run: ./gradlew -Prepo.access.token=${{ secrets.REPO_ACCESS_TOKEN }} -Pdocker.registry=ghcr.io/${{ github.actor }} stage pushImages
|
||||
run: ./gradlew -Pwebsite.push.token=${{ secrets.WEBSITE_PUSH_TOKEN }} -Pdocker.registry=ghcr.io/${{ github.actor }} stage pushImages
|
||||
|
|
|
|||
|
|
@ -1,28 +1,76 @@
|
|||
default:
|
||||
# Template project: https://gitlab.com/pages/jekyll
|
||||
# Docs: https://docs.gitlab.com/ee/pages/
|
||||
image: ruby:3.2
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
- publish
|
||||
- deploy
|
||||
|
||||
.any-job:
|
||||
rules:
|
||||
- if: $CI_SERVER_HOST == "gitlab.mnl.de"
|
||||
|
||||
.gradle-job:
|
||||
extends: .any-job
|
||||
image: registry.mnl.de/org/jgrapes/jdk21-builder:v2
|
||||
cache:
|
||||
- key: dependencies
|
||||
policy: pull-push
|
||||
paths:
|
||||
- .gradle
|
||||
- node_modules
|
||||
- key: "$CI_COMMIT_SHA"
|
||||
policy: pull-push
|
||||
paths:
|
||||
- build
|
||||
- "*/build"
|
||||
before_script:
|
||||
- echo -n $CI_REGISTRY_PASSWORD | podman login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
|
||||
- git switch $CI_COMMIT_REF_NAME
|
||||
- git pull
|
||||
- git reset --hard $CI_COMMIT_SHA
|
||||
|
||||
build-jars:
|
||||
stage: build
|
||||
extends: .gradle-job
|
||||
script:
|
||||
- ./gradlew -Pdocker.registry=$CI_REGISTRY_IMAGE build apidocs
|
||||
|
||||
publish-images:
|
||||
stage: publish
|
||||
extends: .gradle-job
|
||||
script:
|
||||
- ./gradlew -Pdocker.registry=$CI_REGISTRY_IMAGE pushImage
|
||||
|
||||
.pages-job:
|
||||
extends: .any-job
|
||||
image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/ruby:3.2
|
||||
variables:
|
||||
JEKYLL_ENV: production
|
||||
LC_ALL: C.UTF-8
|
||||
before_script:
|
||||
- git fetch origin gh-pages
|
||||
- git checkout gh-pages
|
||||
- gem install bundler
|
||||
- bundle install
|
||||
variables:
|
||||
JEKYLL_ENV: production
|
||||
LC_ALL: C.UTF-8
|
||||
test:
|
||||
|
||||
test-pages:
|
||||
stage: test
|
||||
extends: .pages-job
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == "gh-pages"
|
||||
script:
|
||||
- bundle exec jekyll build -d test
|
||||
artifacts:
|
||||
paths:
|
||||
- test
|
||||
|
||||
pages:
|
||||
stage: deploy
|
||||
script:
|
||||
- bundle exec jekyll build -d public
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
environment: production
|
||||
#publish-pages:
|
||||
# stage: publish
|
||||
# extends: .pages-job
|
||||
# rules:
|
||||
# - if: $CI_COMMIT_BRANCH == "gh-pages"
|
||||
# script:
|
||||
# - bundle exec jekyll build -d public
|
||||
# artifacts:
|
||||
# paths:
|
||||
# - public
|
||||
# environment: production
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@
|
|||
The goal of this project is to provide the means for running Qemu
|
||||
based VMs in Kubernetes pods.
|
||||
|
||||
See the [project's home page](https://mnlipp.github.io/VM-Operator/)
|
||||
See the [project's home page](https://jdrupes.org/vm-operator/)
|
||||
for details.
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ task stage {
|
|||
tc -> tc.findByName("build") }.flatten()
|
||||
}
|
||||
|
||||
if (JavaVersion.current() == JavaVersion.VERSION_17) {
|
||||
if (JavaVersion.current() == JavaVersion.VERSION_21) {
|
||||
// Publish JavaDoc
|
||||
dependsOn gitPublishPush
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ sourceSets {
|
|||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,31 +22,28 @@ configurations {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
markdownDoclet "org.jdrupes.mdoclet:doclet:3.1.0"
|
||||
javadocTaglets "org.jdrupes.taglets:plantuml-taglet:2.1.0"
|
||||
}
|
||||
|
||||
task javadocResources(type: Copy) {
|
||||
into file(docDestinationDir)
|
||||
from ("${rootProject.rootDir}/misc") {
|
||||
include '*.woff2'
|
||||
}
|
||||
markdownDoclet "org.jdrupes.mdoclet:doclet:4.0.0"
|
||||
javadocTaglets "org.jdrupes.taglets:plantuml-taglet:3.0.0"
|
||||
}
|
||||
|
||||
task apidocs (type: JavaExec) {
|
||||
// Does not work on JitPack, no /usr/bin/dot
|
||||
enabled = JavaVersion.current() == JavaVersion.VERSION_17
|
||||
|
||||
dependsOn javadocResources
|
||||
enabled = JavaVersion.current() == JavaVersion.VERSION_21
|
||||
|
||||
outputs.dir(docDestinationDir)
|
||||
|
||||
inputs.file rootProject.file('overview.md')
|
||||
inputs.file "${rootProject.rootDir}/misc/stylesheet.css"
|
||||
inputs.file "${rootProject.rootDir}/misc/javadoc-overwrites.css"
|
||||
|
||||
jvmArgs = ['--add-exports=jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
|
||||
'--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED']
|
||||
main = 'jdk.javadoc.internal.tool.Main'
|
||||
jvmArgs = ['--add-exports=jdk.compiler/com.sun.tools.doclint=ALL-UNNAMED',
|
||||
'--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
|
||||
'--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
|
||||
'--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
|
||||
'--add-exports=jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
|
||||
'--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit=ALL-UNNAMED',
|
||||
'--add-opens=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit.resources.releases=ALL-UNNAMED',
|
||||
'-Duser.language=en', '-Duser.region=US']
|
||||
mainClass = 'jdk.javadoc.internal.tool.Main'
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
// Make sure that other projects' compileClasspaths are resolved
|
||||
|
|
@ -69,8 +66,8 @@ task apidocs (type: JavaExec) {
|
|||
'-package',
|
||||
'-use',
|
||||
'-linksource',
|
||||
'-link', 'https://docs.oracle.com/en/java/javase/17/docs/api/',
|
||||
'-link', 'https://mnlipp.github.io/jgrapes/latest-release/javadoc/',
|
||||
'-link', 'https://docs.oracle.com/en/java/javase/21/docs/api/',
|
||||
'-link', 'https://jgrapes.org/latest-release/javadoc/',
|
||||
'-link', 'https://freemarker.apache.org/docs/api/',
|
||||
'--add-exports', 'jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
|
||||
'--add-exports', 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
|
||||
|
|
@ -88,7 +85,7 @@ task apidocs (type: JavaExec) {
|
|||
'-bottom', rootProject.file("misc/javadoc.bottom.txt").text,
|
||||
'--allow-script-in-comments',
|
||||
'-Xdoclint:-html',
|
||||
'--main-stylesheet', "${rootProject.rootDir}/misc/stylesheet.css",
|
||||
'--add-stylesheet', "${rootProject.rootDir}/misc/javadoc-overwrites.css",
|
||||
'--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.formats.html=ALL-UNNAMED',
|
||||
'-quiet'
|
||||
]
|
||||
|
|
@ -97,23 +94,46 @@ task apidocs (type: JavaExec) {
|
|||
ignoreExitValue true
|
||||
}
|
||||
|
||||
task testJavadoc(type: Javadoc) {
|
||||
enabled = JavaVersion.current() == JavaVersion.VERSION_21
|
||||
|
||||
source = fileTree(dir: 'testfiles', include: '**/*.java')
|
||||
destinationDir = project.file("build/testfiles-gradle")
|
||||
options.docletpath = configurations.markdownDoclet.files.asType(List)
|
||||
options.doclet = 'org.jdrupes.mdoclet.MDoclet'
|
||||
options.overview = 'testfiles/overview.md'
|
||||
options.addStringOption('Xdoclint:-html', '-quiet')
|
||||
|
||||
options.setJFlags([
|
||||
'--add-exports=jdk.compiler/com.sun.tools.doclint=ALL-UNNAMED',
|
||||
'--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
|
||||
'--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
|
||||
'--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
|
||||
'--add-exports=jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
|
||||
'--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit=ALL-UNNAMED',
|
||||
'--add-opens=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit.resources.releases=ALL-UNNAMED'])
|
||||
}
|
||||
// Prepare github authentication for plugins
|
||||
if (System.properties['org.ajoberstar.grgit.auth.username'] == null) {
|
||||
System.setProperty('org.ajoberstar.grgit.auth.username',
|
||||
project.rootProject.properties['repo.access.token'] ?: "nouser")
|
||||
project.rootProject.properties['website.push.token'] ?: "nouser")
|
||||
}
|
||||
|
||||
gitPublish {
|
||||
repoUri = 'https://github.com/mnlipp/VM-Operator.git'
|
||||
branch = 'gh-pages'
|
||||
repoUri = 'https://github.com/mnlipp/jdrupes.org.git'
|
||||
branch = 'main'
|
||||
contents {
|
||||
from("${rootProject.projectDir}/webpages") {
|
||||
include '_layouts/vm-operator.html'
|
||||
include 'vm-operator/**'
|
||||
}
|
||||
from("${rootProject.buildDir}/javadoc") {
|
||||
into 'javadoc'
|
||||
into 'vm-operator/javadoc'
|
||||
}
|
||||
if (!findProject(':org.jdrupes.vmoperator.runner.qemu').isSnapshot
|
||||
&& !findProject(':org.jdrupes.vmoperator.manager').isSnapshot) {
|
||||
from("${rootProject.buildDir}/javadoc") {
|
||||
into 'latest-release/javadoc'
|
||||
into 'vm-operator/latest-release/javadoc'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ spec:
|
|||
containers:
|
||||
- name: vm-operator
|
||||
image: >-
|
||||
ghcr.io/mnlipp/org.jdrupes.vmoperator.manager:3.0.0
|
||||
ghcr.io/mnlipp/org.jdrupes.vmoperator.manager:3.1.1
|
||||
volumeMounts:
|
||||
- name: config
|
||||
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
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
31
gradlew
vendored
|
|
@ -55,7 +55,7 @@
|
|||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
|
@ -83,10 +83,8 @@ done
|
|||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
|
@ -133,10 +131,13 @@ location of your Java installation."
|
|||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
|
|
@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
|
|
@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
|
|
@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then
|
|||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
|
|
|||
20
gradlew.bat
vendored
|
|
@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
|
|||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
|
@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
|
|
|||
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/privacy" target="_top">Privacy</a></p>
|
||||
<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";
|
||||
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>
|
||||
<div>JavaScript is disabled on your browser, terms and privacy links may not be shown correctly.</div>
|
||||
</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>
|
||||
|
|
@ -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'
|
||||
}
|
||||
|
||||
project.ext.gitBranch = grgit.branch.current.name.replace('/', '-')
|
||||
|
||||
task buildImage(type: Exec) {
|
||||
dependsOn installDist
|
||||
inputs.files 'src/org/jdrupes/vmoperator/manager/Containerfile'
|
||||
|
||||
commandLine 'podman', 'build', '--pull',
|
||||
'-t', "${project.name}:${project.version}",\
|
||||
'-t', "${project.name}:${project.gitBranch}",\
|
||||
'-f', 'src/org/jdrupes/vmoperator/manager/Containerfile', '.'
|
||||
}
|
||||
|
||||
task tagLatestImage(type: Exec) {
|
||||
dependsOn buildImage
|
||||
|
||||
enabled = !project.version.contains("SNAPSHOT")
|
||||
&& !project.version.contains("alpha") \
|
||||
&& !project.version.contains("beta") \
|
||||
|| project.rootProject.properties['docker.testRegistry'] \
|
||||
&& project.rootProject.properties['docker.registry'] \
|
||||
== project.rootProject.properties['docker.testRegistry']
|
||||
|
||||
commandLine 'podman', 'tag', "${project.name}:${project.version}",\
|
||||
"${project.name}:latest"
|
||||
}
|
||||
|
||||
task buildLatestImage {
|
||||
dependsOn buildImage
|
||||
dependsOn tagLatestImage
|
||||
}
|
||||
|
||||
task pushImage(type: Exec) {
|
||||
dependsOn buildImage
|
||||
// Don't push without testing first
|
||||
dependsOn test
|
||||
|
||||
def registry = "${project.rootProject.properties['docker.registry']}"
|
||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
||||
"localhost/${project.name}:${project.version}", \
|
||||
"${project.rootProject.properties['docker.registry']}" \
|
||||
+ "/${project.name}:${project.version}"
|
||||
"localhost/${project.name}:${project.gitBranch}", \
|
||||
"${registry}/${project.name}:${project.gitBranch}"
|
||||
|
||||
if (!project.version.contains("SNAPSHOT")) {
|
||||
commandLine 'podman', 'tag', \
|
||||
"${registry}/${project.name}:${project.gitBranch}",\
|
||||
"${registry}/${project.name}:${project.version}"
|
||||
}
|
||||
}
|
||||
|
||||
task pushLatestImage(type: Exec) {
|
||||
dependsOn buildLatestImage
|
||||
task tagAsLatest(type: Exec) {
|
||||
dependsOn pushImage
|
||||
|
||||
enabled = !project.version.contains("SNAPSHOT")
|
||||
&& !project.version.contains("alpha") \
|
||||
|
|
@ -92,28 +83,21 @@ task pushLatestImage(type: Exec) {
|
|||
&& project.rootProject.properties['docker.registry'] \
|
||||
== project.rootProject.properties['docker.testRegistry']
|
||||
|
||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
||||
"localhost/${project.name}:${project.version}", \
|
||||
"${project.rootProject.properties['docker.registry']}" \
|
||||
+ "/${project.name}:latest"
|
||||
def registry = "${project.rootProject.properties['docker.registry']}"
|
||||
commandLine 'podman', 'tag', \
|
||||
"${registry}/${project.name}:${project.version}",\
|
||||
"${registry}/${project.name}:latest"
|
||||
}
|
||||
|
||||
task pushForTest(type: Exec) {
|
||||
dependsOn buildImage
|
||||
|
||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
||||
"localhost/${project.name}:${project.version}", \
|
||||
"${project.rootProject.properties['docker.registry']}" \
|
||||
"localhost/${project.name}:${project.gitBranch}", \
|
||||
"${project.rootProject.properties['docker.testRegistry']}" \
|
||||
+ "/${project.name}:test"
|
||||
}
|
||||
|
||||
task pushImages {
|
||||
// Don't push without testing first
|
||||
dependsOn test
|
||||
dependsOn pushImage
|
||||
dependsOn pushLatestImage
|
||||
}
|
||||
|
||||
test {
|
||||
enabled = project.hasProperty("k8s.testCluster")
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -184,12 +184,8 @@ public class Reconciler extends Component {
|
|||
* @param event the event
|
||||
* @param channel the channel
|
||||
* @throws ApiException the api exception
|
||||
* @throws IOException
|
||||
* @throws ParseException
|
||||
* @throws MalformedTemplateNameException
|
||||
* @throws TemplateNotFoundException
|
||||
* @throws TemplateException
|
||||
* @throws KubectlException
|
||||
* @throws TemplateException the template exception
|
||||
* @throws IOException Signals that an I/O exception has occurred.
|
||||
*/
|
||||
@Handler
|
||||
@SuppressWarnings("PMD.ConfusingTernary")
|
||||
|
|
|
|||
|
|
@ -31,45 +31,34 @@ application {
|
|||
mainClass = 'org.jdrupes.vmoperator.runner.qemu.Runner'
|
||||
}
|
||||
|
||||
project.ext.gitBranch = grgit.branch.current.name.replace('/', '-')
|
||||
|
||||
task buildArchImage(type: Exec) {
|
||||
dependsOn installDist
|
||||
inputs.files 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch'
|
||||
|
||||
commandLine 'podman', 'build', '--pull',
|
||||
'-t', "${project.name}-arch:${project.version}",\
|
||||
'-t', "${project.name}-arch:${project.gitBranch}",\
|
||||
'-f', 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.arch', '.'
|
||||
}
|
||||
|
||||
task tagLatestArchImage(type: Exec) {
|
||||
dependsOn buildArchImage
|
||||
|
||||
enabled = !project.version.contains("SNAPSHOT")
|
||||
&& !project.version.contains("alpha") \
|
||||
&& !project.version.contains("beta") \
|
||||
|| project.rootProject.properties['docker.testRegistry'] \
|
||||
&& project.rootProject.properties['docker.registry'] \
|
||||
== project.rootProject.properties['docker.testRegistry']
|
||||
|
||||
commandLine 'podman', 'tag', "${project.name}-arch:${project.version}",\
|
||||
"${project.name}-arch:latest"
|
||||
}
|
||||
|
||||
task buildLatestArchImage {
|
||||
dependsOn buildArchImage
|
||||
dependsOn tagLatestArchImage
|
||||
}
|
||||
|
||||
task pushArchImage(type: Exec) {
|
||||
dependsOn buildArchImage
|
||||
|
||||
def registry = "${project.rootProject.properties['docker.registry']}"
|
||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
||||
"localhost/${project.name}-arch:${project.version}", \
|
||||
"${project.rootProject.properties['docker.registry']}" \
|
||||
+ "/${project.name}-arch:${project.version}"
|
||||
"localhost/${project.name}-arch:${project.gitBranch}", \
|
||||
"${registry}/${project.name}-arch:${project.gitBranch}"
|
||||
|
||||
if (!project.version.contains("SNAPSHOT")) {
|
||||
commandLine 'podman', 'tag', \
|
||||
"${registry}/${project.name}-arch:${project.gitBranch}",\
|
||||
"${registry}/${project.name}-arch:${project.version}"
|
||||
}
|
||||
}
|
||||
|
||||
task pushArchLatestImage(type: Exec) {
|
||||
dependsOn buildLatestArchImage
|
||||
task tagAsLatestArch(type: Exec) {
|
||||
dependsOn pushArchImage
|
||||
|
||||
enabled = !project.version.contains("SNAPSHOT")
|
||||
&& !project.version.contains("alpha") \
|
||||
|
|
@ -78,10 +67,10 @@ task pushArchLatestImage(type: Exec) {
|
|||
&& project.rootProject.properties['docker.registry'] \
|
||||
== project.rootProject.properties['docker.testRegistry']
|
||||
|
||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
||||
"localhost/${project.name}-arch:${project.version}", \
|
||||
"${project.rootProject.properties['docker.registry']}" \
|
||||
+ "/${project.name}-arch:latest"
|
||||
def registry = "${project.rootProject.properties['docker.registry']}"
|
||||
commandLine 'podman', 'tag', \
|
||||
"${registry}/${project.name}-arch:${project.version}",\
|
||||
"${registry}/${project.name}-arch:latest"
|
||||
}
|
||||
|
||||
task buildAlpineImage(type: Exec) {
|
||||
|
|
@ -89,40 +78,27 @@ task buildAlpineImage(type: Exec) {
|
|||
inputs.files 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine'
|
||||
|
||||
commandLine 'podman', 'build', '--pull',
|
||||
'-t', "${project.name}-alpine:${project.version}",\
|
||||
'-t', "${project.name}-alpine:${project.gitBranch}",\
|
||||
'-f', 'src/org/jdrupes/vmoperator/runner/qemu/Containerfile.alpine', '.'
|
||||
}
|
||||
|
||||
task tagLatestAlpineImage(type: Exec) {
|
||||
dependsOn buildAlpineImage
|
||||
|
||||
enabled = !project.version.contains("SNAPSHOT")
|
||||
&& !project.version.contains("alpha") \
|
||||
&& !project.version.contains("beta") \
|
||||
|| project.rootProject.properties['docker.testRegistry'] \
|
||||
&& project.rootProject.properties['docker.registry'] \
|
||||
== project.rootProject.properties['docker.testRegistry']
|
||||
|
||||
commandLine 'podman', 'tag', "${project.name}-alpine:${project.version}",\
|
||||
"${project.name}-alpine:latest"
|
||||
}
|
||||
|
||||
task buildLatestAlpineImage {
|
||||
dependsOn buildAlpineImage
|
||||
dependsOn tagLatestAlpineImage
|
||||
}
|
||||
|
||||
task pushAlpineImage(type: Exec) {
|
||||
dependsOn buildAlpineImage
|
||||
|
||||
def registry = "${project.rootProject.properties['docker.registry']}"
|
||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
||||
"localhost/${project.name}-alpine:${project.version}", \
|
||||
"${project.rootProject.properties['docker.registry']}" \
|
||||
+ "/${project.name}-alpine:${project.version}"
|
||||
"localhost/${project.name}-alpine:${project.gitBranch}", \
|
||||
"${registry}/${project.name}-alpine:${project.gitBranch}"
|
||||
|
||||
if (!project.version.contains("SNAPSHOT")) {
|
||||
commandLine 'podman', 'tag', \
|
||||
"${registry}/${project.name}-alpine:${project.gitBranch}",\
|
||||
"${registry}/${project.name}-alpine:${project.version}"
|
||||
}
|
||||
}
|
||||
|
||||
task pushAlpineLatestImage(type: Exec) {
|
||||
dependsOn buildLatestAlpineImage
|
||||
task tagAsLatestAlpine(type: Exec) {
|
||||
dependsOn pushAlpineImage
|
||||
|
||||
enabled = !project.version.contains("SNAPSHOT")
|
||||
&& !project.version.contains("alpha") \
|
||||
|
|
@ -131,16 +107,19 @@ task pushAlpineLatestImage(type: Exec) {
|
|||
&& project.rootProject.properties['docker.registry'] \
|
||||
== project.rootProject.properties['docker.testRegistry']
|
||||
|
||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
||||
"localhost/${project.name}-alpine:${project.version}", \
|
||||
"${project.rootProject.properties['docker.registry']}" \
|
||||
+ "/${project.name}-alpine:latest"
|
||||
def registry = "${project.rootProject.properties['docker.registry']}"
|
||||
commandLine 'podman', 'tag', \
|
||||
"${registry}/${project.name}-alpine:${project.version}",\
|
||||
"${registry}/${project.name}-alpine:latest"
|
||||
}
|
||||
|
||||
task pushImages {
|
||||
task pushImage {
|
||||
dependsOn pushArchImage
|
||||
dependsOn pushArchLatestImage
|
||||
dependsOn pushAlpineImage
|
||||
dependsOn pushAlpineLatestImage
|
||||
}
|
||||
|
||||
task tagAsLatest {
|
||||
dependsOn tagAsLatestArch
|
||||
dependsOn tagAsLatestAlpine
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import org.jdrupes.vmoperator.runner.qemu.commands.QmpOpenTray;
|
|||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpRemoveMedium;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.TrayMovedEvent;
|
||||
import org.jgrapes.core.Channel;
|
||||
import org.jgrapes.core.Component;
|
||||
|
|
@ -69,7 +69,7 @@ public class CdMediaController extends Component {
|
|||
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
|
||||
"PMD.AvoidInstantiatingObjectsInLoops" })
|
||||
public void onConfigureQemu(ConfigureQemu event) {
|
||||
if (event.state() == State.TERMINATING) {
|
||||
if (event.runState() == RunState.TERMINATING) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ public class CdMediaController extends Component {
|
|||
}
|
||||
var driveId = "cd" + cdCounter++;
|
||||
var newFile = Optional.ofNullable(drives[i].file).orElse("");
|
||||
if (event.state() == State.STARTING) {
|
||||
if (event.runState() == RunState.STARTING) {
|
||||
current.put(driveId, newFile);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -116,8 +116,8 @@ public class CdMediaController extends Component {
|
|||
*/
|
||||
@Handler
|
||||
public void onTrayMovedEvent(TrayMovedEvent event) {
|
||||
trayState.put(event.driveId(), event.state());
|
||||
if (event.state() == TrayState.OPEN
|
||||
trayState.put(event.driveId(), event.trayState());
|
||||
if (event.trayState() == TrayState.OPEN
|
||||
&& pending.containsKey(event.driveId())) {
|
||||
changeMedium(event.driveId());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ FROM docker.io/alpine
|
|||
|
||||
RUN apk update
|
||||
|
||||
RUN apk add qemu-system-x86_64 qemu-modules ovmf swtpm openjdk17 mtools
|
||||
RUN apk add qemu-system-x86_64 qemu-modules ovmf swtpm openjdk21 mtools
|
||||
|
||||
RUN mkdir -p /etc/qemu && echo "allow all" > /etc/qemu/bridge.conf
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
FROM archlinux/archlinux:latest
|
||||
FROM docker.io/archlinux/archlinux:latest
|
||||
|
||||
RUN systemd-firstboot
|
||||
|
||||
RUN pacman-key --init \
|
||||
&& pacman -Sy --noconfirm archlinux-keyring && pacman -Su --noconfirm \
|
||||
&& pacman -S --noconfirm which qemu-full virtiofsd \
|
||||
edk2-ovmf swtpm iproute2 bridge-utils jre17-openjdk-headless \
|
||||
edk2-ovmf swtpm iproute2 bridge-utils jre21-openjdk-headless \
|
||||
mtools \
|
||||
&& pacman -Scc --noconfirm
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import org.jdrupes.vmoperator.runner.qemu.events.CpuAdded;
|
|||
import org.jdrupes.vmoperator.runner.qemu.events.CpuDeleted;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState;
|
||||
import org.jgrapes.core.Channel;
|
||||
import org.jgrapes.core.Component;
|
||||
import org.jgrapes.core.annotation.Handler;
|
||||
|
|
@ -64,7 +64,7 @@ public class CpuController extends Component {
|
|||
*/
|
||||
@Handler
|
||||
public void onConfigureQemu(ConfigureQemu event) {
|
||||
if (event.state() == State.TERMINATING) {
|
||||
if (event.runState() == RunState.TERMINATING) {
|
||||
return;
|
||||
}
|
||||
Optional.ofNullable(event.configuration().vm.currentCpus)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetDisplayPassword;
|
|||
import org.jdrupes.vmoperator.runner.qemu.commands.QmpSetPasswordExpiry;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.ConfigureQemu;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState;
|
||||
import org.jgrapes.core.Channel;
|
||||
import org.jgrapes.core.Component;
|
||||
import org.jgrapes.core.annotation.Handler;
|
||||
|
|
@ -67,7 +67,7 @@ public class DisplayController extends Component {
|
|||
*/
|
||||
@Handler
|
||||
public void onConfigureQemu(ConfigureQemu event) {
|
||||
if (event.state() == State.TERMINATING) {
|
||||
if (event.runState() == RunState.TERMINATING) {
|
||||
return;
|
||||
}
|
||||
protocol
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ import org.jdrupes.vmoperator.runner.qemu.events.Exit;
|
|||
import org.jdrupes.vmoperator.runner.qemu.events.MonitorCommand;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.QmpConfigured;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState;
|
||||
import org.jdrupes.vmoperator.util.ExtendedObjectWrapper;
|
||||
import org.jdrupes.vmoperator.util.FsdUtils;
|
||||
import org.jgrapes.core.Channel;
|
||||
|
|
@ -217,7 +217,7 @@ public class Runner extends Component {
|
|||
private CommandDefinition qemuDefinition;
|
||||
private final QemuMonitor qemuMonitor;
|
||||
private Integer resetCounter;
|
||||
private State state = State.INITIALIZING;
|
||||
private RunState state = RunState.INITIALIZING;
|
||||
|
||||
/** Preparatory actions for QEMU start */
|
||||
@SuppressWarnings("PMD.FieldNamingConventions")
|
||||
|
|
@ -467,7 +467,7 @@ public class Runner extends Component {
|
|||
*/
|
||||
@Handler
|
||||
public void onStarted(Started event) {
|
||||
state = State.STARTING;
|
||||
state = RunState.STARTING;
|
||||
rep.fire(new RunnerStateChange(state, "RunnerStarted",
|
||||
"Runner has been started"));
|
||||
// Start first process(es)
|
||||
|
|
@ -618,9 +618,9 @@ public class Runner extends Component {
|
|||
*/
|
||||
@Handler(priority = -1000)
|
||||
public void onConfigureQemuFinal(ConfigureQemu event) {
|
||||
if (state == State.STARTING) {
|
||||
if (state == RunState.STARTING) {
|
||||
fire(new MonitorCommand(new QmpCont()));
|
||||
state = State.RUNNING;
|
||||
state = RunState.RUNNING;
|
||||
rep.fire(new RunnerStateChange(state, "VmStarted",
|
||||
"Qemu has been configured and is continuing"));
|
||||
}
|
||||
|
|
@ -633,7 +633,7 @@ public class Runner extends Component {
|
|||
*/
|
||||
@Handler
|
||||
public void onConfigureQemu(ConfigureQemu event) {
|
||||
if (state == State.RUNNING) {
|
||||
if (state == RunState.RUNNING) {
|
||||
if (resetCounter != null
|
||||
&& event.configuration().resetCounter != null
|
||||
&& event.configuration().resetCounter > resetCounter) {
|
||||
|
|
@ -659,14 +659,14 @@ public class Runner extends Component {
|
|||
return;
|
||||
}
|
||||
// No other process(es) may exit during startup
|
||||
if (state == State.STARTING) {
|
||||
if (state == RunState.STARTING) {
|
||||
logger.severe(() -> "Process " + procDef.name
|
||||
+ " has exited with value " + event.exitValue()
|
||||
+ " during startup.");
|
||||
rep.fire(new Stop());
|
||||
return;
|
||||
}
|
||||
if (procDef.equals(qemuDefinition) && state == State.RUNNING) {
|
||||
if (procDef.equals(qemuDefinition) && state == RunState.RUNNING) {
|
||||
rep.fire(new Exit(event.exitValue()));
|
||||
}
|
||||
logger.info(() -> "Process " + procDef.name
|
||||
|
|
@ -693,7 +693,7 @@ public class Runner extends Component {
|
|||
*/
|
||||
@Handler(priority = 10_000)
|
||||
public void onStopFirst(Stop event) {
|
||||
state = State.TERMINATING;
|
||||
state = RunState.TERMINATING;
|
||||
rep.fire(new RunnerStateChange(state, "VmTerminating",
|
||||
"The VM is being shut down", exitStatus != 0));
|
||||
}
|
||||
|
|
@ -705,14 +705,14 @@ public class Runner extends Component {
|
|||
*/
|
||||
@Handler(priority = -10_000)
|
||||
public void onStopLast(Stop event) {
|
||||
state = State.STOPPED;
|
||||
state = RunState.STOPPED;
|
||||
rep.fire(new RunnerStateChange(state, "VmStopped",
|
||||
"The VM has been shut down"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("PMD.ConfusingArgumentToVarargsMethod")
|
||||
private void shutdown() {
|
||||
if (!Set.of(State.TERMINATING, State.STOPPED).contains(state)) {
|
||||
if (!Set.of(RunState.TERMINATING, RunState.STOPPED).contains(state)) {
|
||||
fire(new Stop());
|
||||
}
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ import org.jdrupes.vmoperator.runner.qemu.events.DisplayPasswordChanged;
|
|||
import org.jdrupes.vmoperator.runner.qemu.events.Exit;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.HotpluggableCpuStatus;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.ShutdownEvent;
|
||||
import org.jdrupes.vmoperator.util.GsonPtr;
|
||||
import org.jgrapes.core.Channel;
|
||||
|
|
@ -65,8 +65,8 @@ import org.jgrapes.util.events.InitialConfiguration;
|
|||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
||||
public class StatusUpdater extends Component {
|
||||
|
||||
private static final Set<State> RUNNING_STATES
|
||||
= Set.of(State.RUNNING, State.TERMINATING);
|
||||
private static final Set<RunState> RUNNING_STATES
|
||||
= Set.of(RunState.RUNNING, RunState.TERMINATING);
|
||||
|
||||
private String namespace;
|
||||
private String vmName;
|
||||
|
|
@ -240,11 +240,11 @@ public class StatusUpdater extends Component {
|
|||
updateRunningCondition(event, from, cond);
|
||||
}
|
||||
});
|
||||
if (event.state() == State.STARTING) {
|
||||
if (event.runState() == RunState.STARTING) {
|
||||
status.addProperty("ram", GsonPtr.to(from.data())
|
||||
.getAsString("spec", "vm", "maximumRam").orElse("0"));
|
||||
status.addProperty("cpus", 1);
|
||||
} else if (event.state() == State.STOPPED) {
|
||||
} else if (event.runState() == RunState.STOPPED) {
|
||||
status.addProperty("ram", "0");
|
||||
status.addProperty("cpus", 0);
|
||||
}
|
||||
|
|
@ -252,7 +252,7 @@ public class StatusUpdater extends Component {
|
|||
});
|
||||
|
||||
// Maybe stop VM
|
||||
if (event.state() == State.TERMINATING && !event.failed()
|
||||
if (event.runState() == RunState.TERMINATING && !event.failed()
|
||||
&& guestShutdownStops && shutdownByGuest) {
|
||||
logger.info(() -> "Stopping VM because of shutdown by guest.");
|
||||
var res = vmStub.patch(V1Patch.PATCH_FORMAT_JSON_PATCH,
|
||||
|
|
@ -277,13 +277,13 @@ public class StatusUpdater extends Component {
|
|||
K8sDynamicModel from, JsonObject cond) {
|
||||
boolean reportedRunning
|
||||
= "True".equals(cond.get("status").getAsString());
|
||||
if (RUNNING_STATES.contains(event.state())
|
||||
if (RUNNING_STATES.contains(event.runState())
|
||||
&& !reportedRunning) {
|
||||
cond.addProperty("status", "True");
|
||||
cond.addProperty("lastTransitionTime",
|
||||
Instant.now().toString());
|
||||
}
|
||||
if (!RUNNING_STATES.contains(event.state())
|
||||
if (!RUNNING_STATES.contains(event.runState())
|
||||
&& reportedRunning) {
|
||||
cond.addProperty("status", "False");
|
||||
cond.addProperty("lastTransitionTime",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
package org.jdrupes.vmoperator.runner.qemu.events;
|
||||
|
||||
import org.jdrupes.vmoperator.runner.qemu.Configuration;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.State;
|
||||
import org.jdrupes.vmoperator.runner.qemu.events.RunnerStateChange.RunState;
|
||||
import org.jgrapes.core.Channel;
|
||||
import org.jgrapes.core.Event;
|
||||
|
||||
|
|
@ -34,14 +34,14 @@ import org.jgrapes.core.Event;
|
|||
public class ConfigureQemu extends Event<Void> {
|
||||
|
||||
private final Configuration configuration;
|
||||
private final State state;
|
||||
private final RunState state;
|
||||
|
||||
/**
|
||||
* Instantiates a new configuration event.
|
||||
*
|
||||
* @param channels the channels
|
||||
*/
|
||||
public ConfigureQemu(Configuration configuration, State state,
|
||||
public ConfigureQemu(Configuration configuration, RunState state,
|
||||
Channel... channels) {
|
||||
super(channels);
|
||||
this.state = state;
|
||||
|
|
@ -62,7 +62,7 @@ public class ConfigureQemu extends Event<Void> {
|
|||
*
|
||||
* @return the state
|
||||
*/
|
||||
public State state() {
|
||||
public RunState runState() {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,11 +31,11 @@ public class RunnerStateChange extends Event<Void> {
|
|||
/**
|
||||
* The state.
|
||||
*/
|
||||
public enum State {
|
||||
public enum RunState {
|
||||
INITIALIZING, STARTING, RUNNING, TERMINATING, STOPPED
|
||||
}
|
||||
|
||||
private final State state;
|
||||
private final RunState state;
|
||||
private final String reason;
|
||||
private final String message;
|
||||
private final boolean failed;
|
||||
|
|
@ -48,7 +48,7 @@ public class RunnerStateChange extends Event<Void> {
|
|||
* @param message the message
|
||||
* @param channels the channels
|
||||
*/
|
||||
public RunnerStateChange(State state, String reason, String message,
|
||||
public RunnerStateChange(RunState state, String reason, String message,
|
||||
Channel... channels) {
|
||||
this(state, reason, message, false, channels);
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ public class RunnerStateChange extends Event<Void> {
|
|||
* @param failed the failed
|
||||
* @param channels the channels
|
||||
*/
|
||||
public RunnerStateChange(State state, String reason, String message,
|
||||
public RunnerStateChange(RunState state, String reason, String message,
|
||||
boolean failed, Channel... channels) {
|
||||
super(channels);
|
||||
this.state = state;
|
||||
|
|
@ -76,7 +76,7 @@ public class RunnerStateChange extends Event<Void> {
|
|||
*
|
||||
* @return the state
|
||||
*/
|
||||
public State state() {
|
||||
public RunState runState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public class TrayMovedEvent extends MonitorEvent {
|
|||
*
|
||||
* @return the tray state
|
||||
*/
|
||||
public TrayState state() {
|
||||
public TrayState trayState() {
|
||||
return data().get("tray-open").asBoolean()
|
||||
? TrayState.OPEN
|
||||
: TrayState.CLOSED;
|
||||
|
|
|
|||
3158
package-lock.json
generated
|
|
@ -21,6 +21,10 @@
|
|||
"typedoc-plugin-missing-exports": "^2.1.0",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"overrides": {
|
||||
"node-gyp": "^10.1.0",
|
||||
"glob": "^9.0.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": "eslint:recommended",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM alpine:3.19
|
||||
FROM docker.io/alpine:3.19
|
||||
|
||||
RUN apk update &&\
|
||||
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.
|
||||