Compare commits
383 commits
develop/v3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c6c6358426 | |||
| 470c266157 | |||
| 7b8df80828 | |||
| fccf2a6b65 | |||
| 00e9affee4 | |||
| fa84110e1d | |||
| 76b579c404 | |||
| a5433c869b | |||
| 10f3028f06 | |||
| b7fad4614d | |||
| 7d298ce24b | |||
| 6ef4c2aaa2 | |||
| 5bcf0ba051 | |||
| d67f374de7 | |||
| 2b3420c801 | |||
| bd54d293eb | |||
| cb2ae7c33e | |||
| 85a9b41046 | |||
| fb976802cf | |||
| af112bb66b | |||
| 592b30f6c5 | |||
| c716e32534 | |||
| c79d678a2a | |||
| f30ea79abb | |||
| d7d5c860a2 | |||
| 991763f228 | |||
| db49f5ba2f | |||
| 2e70ef2b98 | |||
| e8097d87d9 | |||
| 7a70d73330 | |||
| 3143a1be93 | |||
| 4bcbafb4f1 | |||
| 331b6d8d61 | |||
| 725fb663c8 | |||
| d9799df861 | |||
| fe1d56517b | |||
| 359b1fdb84 | |||
| 16a15bc9ad | |||
| 7644e65ab0 | |||
| dbc89e6e09 | |||
| 9baf9b7673 | |||
| 3686629a28 | |||
| 5991fe0d2d | |||
| 3b0a4c8a23 | |||
| 5ca45d7620 | |||
| efd489b22f | |||
| 9644e5fd83 | |||
| fe18bb3cdf | |||
| 9a557f5019 | |||
| fd0f4f8eb2 | |||
| 9bd17e8899 | |||
| 227c097c01 | |||
| ce4d0bfb72 | |||
| 017607f2e2 | |||
| fcdb537f35 | |||
| 5309460fbf | |||
| dc228295d1 | |||
| 1b1e5ffb8c | |||
| a3d6db3178 | |||
| 197c21bc32 | |||
| 8bc413b2da | |||
| ba582d877e | |||
| 3a4404b758 | |||
| f0ebea5353 | |||
| 084bdd1f46 | |||
| 5947bd3684 | |||
| c02b3d99cb | |||
| 407aa4b4d1 | |||
| 3df01fcad0 | |||
| 2d16cbc352 | |||
| 2a3774ae24 | |||
| d637cb2c72 | |||
| f493a2c582 | |||
| 72e1b8a580 | |||
| ecb43db83e | |||
| 2a33f468f2 | |||
| 36877666f3 | |||
| fae75dafa9 | |||
| 46f079504c | |||
| 5d0c6c6423 | |||
| 19968ab73e | |||
| 68a688c4ce | |||
| 44868464b9 | |||
| 61286a528c | |||
| ce1a9afec7 | |||
| 591278a07f | |||
| 29bc6f539c | |||
| 2aa4116e95 | |||
| 7104984ac7 | |||
| 7f7306fc4a | |||
| 7130c128bb | |||
| 3557e5fc27 | |||
| f3907ffae9 | |||
| 9459c367ac | |||
| c5a00acf3d | |||
| 1a8412d767 | |||
| 21d2fe2dbd | |||
| 607379b06d | |||
| c51da8650a | |||
| 12c72b3f52 | |||
| 7dc68b5ac7 | |||
| 5ae162445c | |||
| fb73a17748 | |||
| 961098a984 | |||
| 0e57a4e862 | |||
| 2524172c12 | |||
| 7437a17c9f | |||
| 7e650bf980 | |||
| 940913cf89 | |||
| 04ccdd7dee | |||
| bfe4c2bb32 | |||
| 28b1903acc | |||
| dfe3038463 | |||
| 701194799f | |||
| 17e2a7c6f0 | |||
| eb3979dc83 | |||
| e871fc059b | |||
| d4fdd4209f | |||
| 51a72a162d | |||
| fa53a07a52 | |||
| 6b6a33e702 | |||
| cc78c38efe | |||
| c004265f5e | |||
| 5c7a9f6e5f | |||
| 03fdabe85a | |||
| 214085119c | |||
| 30bc119178 | |||
| 987f634f40 | |||
| 083c6db2da | |||
| 7670857d0a | |||
| d6e2a92fe8 | |||
| d8cff8b942 | |||
| 4965845f3d | |||
| a0dfd25192 | |||
| fd0bcc9307 | |||
| 0e28bcd038 | |||
| 60349bca78 | |||
| 199cd8185e | |||
| f6338758d8 | |||
| 3a94602a0d | |||
| 07fb07a6a4 | |||
| 687a050ec4 | |||
| 2f6b3d2127 | |||
| 05d53c58b1 | |||
| d7af1f5d06 | |||
| 6b7c78ed2c | |||
| 502842f486 | |||
| 5b7531c5e5 | |||
| d0298eb7e8 | |||
| 01db49397a | |||
| f8cc26e657 | |||
| 41ae658e0c | |||
| e822d472f9 | |||
| 4a242c4657 | |||
| 62a7210117 | |||
| 5e282c4d2b | |||
| 5366e24092 | |||
| 3152ff842b | |||
| b409443499 | |||
| 5ec220d0a6 | |||
| 5cbdab9da8 | |||
| b4cb3b8694 | |||
| 59bf4937ef | |||
| 3119349450 | |||
| b4bc0c7b0f | |||
| a1e941276e | |||
| 4a7a309f07 | |||
| d2c39dc06a | |||
| 2119c215fc | |||
| d1bc335db9 | |||
| c6704c886f | |||
| bc33640c98 | |||
| ddab466fd0 | |||
| c45c452c83 | |||
| e3b5f5a04d | |||
| f236b376ae | |||
| 558f4d96c9 | |||
| 5b8b47f95c | |||
| 3012da3e87 | |||
| 0828d03835 | |||
| 81b128e4a3 | |||
| e291352828 | |||
| 5ad052ffe4 | |||
| c582763fbf | |||
| 777ae73c74 | |||
| bccc4ac219 | |||
| ec8152bd51 | |||
| e4bba582a0 | |||
| 0287ae7998 | |||
| 46cb2466fe | |||
| 3e713b4ff2 | |||
| 0ded0ff9a9 | |||
| 5078001f4b | |||
| 1fc26647b6 | |||
| aea8b9540e | |||
| d27339b1e9 | |||
| d5e589709f | |||
| 21108771d9 | |||
| b7ea6860ff | |||
| 85a4521299 | |||
| b250398213 | |||
| 54747b25e8 | |||
| 9986e4c8bf | |||
| b5ae22a8ea | |||
| b78b33a6f1 | |||
| b159bae5da | |||
| 23bc41d68d | |||
| 6a1273e701 | |||
| 4fc0d6fc63 | |||
| ecd7ba7baf | |||
| 150b9f2908 | |||
| 29dd6aab82 | |||
| 99c96e44c3 | |||
| 1f4d69075a | |||
| ad79e8542a | |||
| e447a944dc | |||
| 49566584a2 | |||
| e4e00c8ec8 | |||
| ebda41346a | |||
| 8d96307bb5 | |||
| af41c78c07 | |||
| 5cd4edcec1 | |||
| 85be5b9cbf | |||
| 50ad911265 | |||
| 86f6ece264 | |||
| 1b5ad5b73e | |||
| 3ca632c8da | |||
| e7cc7cc879 | |||
| 981cbe2744 | |||
| 224855efd3 | |||
| aaf1a0c545 | |||
| 53a58a2aca | |||
| 574ad5226b | |||
| a0d626cc31 | |||
| 2a70c74234 | |||
| 5d722abd2e | |||
| 877d4c69cd | |||
| 80d4165500 | |||
| a5ddf6ac97 | |||
| 9318b1279a | |||
| fb69c1d793 | |||
| edc3596e7d | |||
| ba18e1f0d0 | |||
| 8799bcc8f2 | |||
| 1cb90b0c94 | |||
| 6d5ba8829c | |||
| d060a9334a | |||
| 9b47ad3136 | |||
| 76be59a5b3 | |||
| 5bd6700541 | |||
| bd5227fda3 | |||
| 4943baf3e3 | |||
| 15ac0721a6 | |||
| db7fbe2b7c | |||
| 84ac4bb28c | |||
| c3428ea4a5 | |||
| 2dc93f1370 | |||
| 367aebeee5 | |||
| 77cfcff2ed | |||
| 4c600e7118 | |||
| e839f7b3b2 | |||
| 4ceaaa9fa2 | |||
| 2be88d0f34 | |||
| c361f9296d | |||
| dc21dc8a7b | |||
| 64035d8dc1 | |||
| 22446c3618 | |||
| 5efef2a083 | |||
| eabb2d9cf0 | |||
| 27f983c18d | |||
| 00adeba625 | |||
| 0f4768d707 | |||
| 5a68b976c8 | |||
| 4690b897e9 | |||
| 043666a932 | |||
| bc0d25d00e | |||
| 44ae5d405b | |||
| e7da41f838 | |||
| 5d90a6a8a9 | |||
| 1495301ec8 | |||
| f513e6c395 | |||
| dec4c11785 | |||
| 13cd262a47 | |||
| a7ee3d0515 | |||
| 31758b5ef1 | |||
| 97e94a8e9a | |||
| 28df6ede15 | |||
| d8132de6c2 | |||
| 558d8f9548 | |||
| dc7382dc86 | |||
| 6e3f554d8d | |||
| e864f677c3 | |||
| 93a1a2b2f9 | |||
| 69507b540c | |||
| 0ba8d922ef | |||
| 4ea568ea17 | |||
| 811164f7b9 | |||
| f1d973502d | |||
| 4d447717c2 | |||
| 9773207307 | |||
| 82a6d53156 | |||
| 5ec7f58bbd | |||
| 228322748b | |||
| 40cbeb694b | |||
| 5897b4b386 | |||
| 65ceed93b6 | |||
| 12d6745d75 | |||
| 0aaa375ffc | |||
| b8aa925a49 | |||
| 355eded86b | |||
| 090d504b77 | |||
| e5fd45ebcb | |||
| 12408143a7 | |||
| c7b65ca581 | |||
| 4d76225442 | |||
| 9019907224 | |||
| 5b209c935e | |||
| 3d446836b5 | |||
| 2d51421e19 | |||
| abe06b4658 | |||
| a9c31a378e | |||
| c8781c2d8e | |||
| 8e692a03fe | |||
| 3708244571 | |||
| 23703bad3c | |||
| 80fe921e6e | |||
| 1bc63abadf | |||
| 54445ef531 | |||
| 31a3f79e2a | |||
| 83908b7cfd | |||
| 525696ffe9 | |||
| 5c736faf09 | |||
| 69a9629ea9 | |||
| 9856284e39 | |||
| bef47b308d | |||
| 82eb6671a3 | |||
| 4384c18910 | |||
| 51c1d8d7a9 | |||
| 605ede7cf3 | |||
| 4c04bb0e0a | |||
|
|
45e271e6d0 | ||
|
|
959a35ca9e | ||
|
|
a62b8c2899 | ||
| 1b2d7ec330 | |||
| e7cdaea205 | |||
| 33856fffc2 | |||
| e3fc4747e4 | |||
| fcf0c1d1af | |||
| 8f8a38771e | |||
| f45c5698d1 | |||
| 299bded9de | |||
| 9a5e1800ff | |||
| 8802666944 | |||
| 63e77c0a8a | |||
| e3da87d94f | |||
| b74de67c6d | |||
| 6852c575ae | |||
| 1fe7960d24 | |||
| fc29786afe | |||
| 8a434d8410 | |||
| e994fa1543 | |||
| 8b83a0cbc8 | |||
| 7df0cc386c | |||
| 12ca211fdb | |||
| e830815400 | |||
| 28691b6443 | |||
| 86837bc81b | |||
| a50809211d | |||
| 63c03dae81 | |||
| 7dea95660c | |||
| 128ffbc2ad | |||
| 9d5f3cf702 | |||
| 6a2d711643 | |||
| f0ccb83b39 | |||
| 98ce74f42b | |||
| 92f4b3aaf8 | |||
|
|
7f80f4c6e9 | ||
|
|
10182efea1 | ||
| 9c31f574b8 | |||
| d5c9a0c302 | |||
| 6213aa5970 | |||
| bbd9d3baff | |||
|
|
65a5cfd286 |
305 changed files with 23947 additions and 6660 deletions
6
.github/workflows/gradle.yml
vendored
6
.github/workflows/gradle.yml
vendored
|
|
@ -22,10 +22,10 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install graphviz
|
- name: Install graphviz
|
||||||
run: sudo apt-get install graphviz
|
run: sudo apt-get install graphviz
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew -Prepo.access.token=${{ secrets.REPO_ACCESS_TOKEN }} stage
|
run: ./gradlew -Pwebsite.push.token=${{ secrets.WEBSITE_PUSH_TOKEN }} stage
|
||||||
|
|
|
||||||
89
.github/workflows/jekyll.yml
vendored
Normal file
89
.github/workflows/jekyll.yml
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
# This workflow uses actions that are not certified by GitHub.
|
||||||
|
# They are provided by a third-party and are governed by
|
||||||
|
# separate terms of service, privacy policy, and support
|
||||||
|
# documentation.
|
||||||
|
|
||||||
|
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
|
||||||
|
name: Deploy Jekyll site to Pages
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Runs on pushes targeting the default branch
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
|
||||||
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
# Allow only one concurrent deployment, skipping runs queued between
|
||||||
|
# the run in-progress and latest queued. However, do NOT cancel
|
||||||
|
# in-progress runs as we want to allow these production deployments
|
||||||
|
# to complete.
|
||||||
|
concurrency:
|
||||||
|
group: "pages"
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Build job
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Ruby
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
ruby-version: '3.3' # Not needed with a .ruby-version file
|
||||||
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||||
|
cache-version: 0 # Increment this number if you need to re-download cached gems
|
||||||
|
working-directory: webpages
|
||||||
|
- name: Setup Pages
|
||||||
|
id: pages
|
||||||
|
uses: actions/configure-pages@v5
|
||||||
|
- name: Build with Jekyll
|
||||||
|
# Outputs to the './_site' directory by default
|
||||||
|
run: cd webpages && bundle exec jekyll build
|
||||||
|
env:
|
||||||
|
JEKYLL_ENV: production
|
||||||
|
- name: Install graphviz
|
||||||
|
run: sudo apt-get install graphviz
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: '21'
|
||||||
|
distribution: 'temurin'
|
||||||
|
- name: Build apidocs
|
||||||
|
run: ./gradlew apidocs
|
||||||
|
- name: Copy javadoc
|
||||||
|
run: cp -a build/javadoc webpages/_site/
|
||||||
|
- name: Generate the sitemap
|
||||||
|
uses: cicirello/generate-sitemap@v1
|
||||||
|
with:
|
||||||
|
path-to-root: webpages/_site
|
||||||
|
base-url-path: https://vm-operator.jdrupes.org
|
||||||
|
- name: Index pagefind
|
||||||
|
run: cd webpages && npx pagefind --source "_site"
|
||||||
|
- name: Upload artifact
|
||||||
|
# Automatically uploads an artifact from the './_site' directory by default
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: './webpages/_site'
|
||||||
|
|
||||||
|
# Deployment job
|
||||||
|
deploy:
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
|
|
@ -18,10 +18,9 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: main
|
|
||||||
- name: Install graphviz
|
- name: Install graphviz
|
||||||
run: sudo apt-get install graphviz
|
run: sudo apt-get install graphviz
|
||||||
- name: Install podman
|
- name: Install podman
|
||||||
|
|
@ -32,10 +31,10 @@ jobs:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '21'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
- name: Push with Gradle
|
- name: Push with Gradle
|
||||||
run: ./gradlew -Prepo.access.token=${{ secrets.REPO_ACCESS_TOKEN }} -Pdocker.registry=ghcr.io/${{ github.actor }} stage pushImages
|
run: ./gradlew -Pwebsite.push.token=${{ secrets.WEBSITE_PUSH_TOKEN }} -Pdocker.registry=ghcr.io/${{ github.actor }} stage publishImage
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
default:
|
|
||||||
# Template project: https://gitlab.com/pages/jekyll
|
|
||||||
# Docs: https://docs.gitlab.com/ee/pages/
|
|
||||||
image: ruby:3.2
|
|
||||||
before_script:
|
|
||||||
- git fetch origin gh-pages
|
|
||||||
- git checkout gh-pages
|
|
||||||
- gem install bundler
|
|
||||||
- bundle install
|
|
||||||
variables:
|
|
||||||
JEKYLL_ENV: production
|
|
||||||
LC_ALL: C.UTF-8
|
|
||||||
test:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- bundle exec jekyll build -d test
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- test
|
|
||||||
|
|
||||||
pages:
|
|
||||||
stage: deploy
|
|
||||||
script:
|
|
||||||
- bundle exec jekyll build -d public
|
|
||||||
artifacts:
|
|
||||||
paths:
|
|
||||||
- public
|
|
||||||
environment: production
|
|
||||||
30
.markdownlint.yaml
Normal file
30
.markdownlint.yaml
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# See [rules](https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.yaml)
|
||||||
|
|
||||||
|
# Default state for all rules
|
||||||
|
default: true
|
||||||
|
|
||||||
|
# MD007/ul-indent : Unordered list indentation :
|
||||||
|
# https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md007.md
|
||||||
|
MD007:
|
||||||
|
# Spaces for indent
|
||||||
|
indent: 2
|
||||||
|
# Whether to indent the first level of the list
|
||||||
|
start_indented: true
|
||||||
|
# Spaces for first level indent (when start_indented is set)
|
||||||
|
start_indent: 2
|
||||||
|
|
||||||
|
# MD025/single-title/single-h1 : Multiple top-level headings in the same document :
|
||||||
|
# https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md025.md
|
||||||
|
MD025:
|
||||||
|
# Heading level
|
||||||
|
level: 1
|
||||||
|
# RegExp for matching title in front matter (disable)
|
||||||
|
front_matter_title: ""
|
||||||
|
|
||||||
|
# MD036/no-emphasis-as-heading : Emphasis used instead of a heading :
|
||||||
|
# https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md036.md
|
||||||
|
MD036: false
|
||||||
|
|
||||||
|
# MD043/required-headings : Required heading structure :
|
||||||
|
# https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md043.md
|
||||||
|
MD043: false
|
||||||
38
.woodpecker/build.yaml
Normal file
38
.woodpecker/build.yaml
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
evaluate: 'CI_SYSTEM_HOST == "woodpecker.mnl.de"'
|
||||||
|
|
||||||
|
clone:
|
||||||
|
- name: git
|
||||||
|
image: woodpeckerci/plugin-git
|
||||||
|
settings:
|
||||||
|
partial: false
|
||||||
|
tags: true
|
||||||
|
depth: 0
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: prepare
|
||||||
|
image: alpine
|
||||||
|
commands:
|
||||||
|
# Because we run the next step as user 1000 to make podman work:
|
||||||
|
- mkdir /woodpecker/workflow
|
||||||
|
- chown 1000:1000 /woodpecker/workflow
|
||||||
|
- chown -R 1000:1000 $CI_WORKSPACE
|
||||||
|
|
||||||
|
- name: build-jars
|
||||||
|
image: registry.mnl.de/mnl/jdk21-builder:v4
|
||||||
|
environment:
|
||||||
|
HOME: /woodpecker/workflow
|
||||||
|
REGISTRY: registry.mnl.de
|
||||||
|
REGISTRY_USER: mnl
|
||||||
|
REGISTRY_TOKEN:
|
||||||
|
from_secret: REGISTRY_TOKEN
|
||||||
|
commands:
|
||||||
|
- echo $REGISTRY_TOKEN | podman login -u $REGISTRY_USER --password-stdin $REGISTRY
|
||||||
|
- ./gradlew -Pdocker.registry=$REGISTRY/$REGISTRY_USER build apidocs publishImage
|
||||||
|
backend_options:
|
||||||
|
kubernetes:
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
runAsUser: 1000
|
||||||
|
runAsGroup: 1000
|
||||||
22
README.md
22
README.md
|
|
@ -3,11 +3,23 @@
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
# Run Qemu in Kubernetes Pods
|
# Run QEMU/KVM in Kubernetes Pods
|
||||||
|
|
||||||
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/)
|
This project provides an easy to use and flexible solution for running
|
||||||
|
QEMU/KVM based VMs in Kubernetes pods.
|
||||||
|
|
||||||
|
The central component of this solution is the kubernetes operator that
|
||||||
|
manages "runners". These run in pods and are used to start and manage
|
||||||
|
the QEMU/KVM process for the VMs (optionally together with a SW-TPM).
|
||||||
|
|
||||||
|
A web GUI for administrators provides an overview of the VMs together
|
||||||
|
with some basic control over the VMs. A web GUI for users provides an
|
||||||
|
interface to access and optionally start, stop and reset the VMs.
|
||||||
|
|
||||||
|
Advanced features of the operator include pooling of VMs and automatic
|
||||||
|
login.
|
||||||
|
|
||||||
|
See the [project's home page](https://vm-operator.jdrupes.org/)
|
||||||
for details.
|
for details.
|
||||||
|
|
||||||
|
|
|
||||||
12
build.gradle
12
build.gradle
|
|
@ -5,9 +5,10 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'org.ajoberstar.grgit' version '5.2.0' apply false
|
id 'org.ajoberstar.grgit' version '5.2.0'
|
||||||
id 'org.ajoberstar.git-publish' version '4.2.0' apply false
|
id 'org.ajoberstar.git-publish' version '4.2.0' apply false
|
||||||
id 'pl.allegro.tech.build.axion-release' version '1.15.0' apply false
|
id 'pl.allegro.tech.build.axion-release' version '1.17.2' apply false
|
||||||
|
id 'org.jdrupes.vmoperator.versioning-conventions'
|
||||||
id 'org.jdrupes.vmoperator.java-doc-conventions'
|
id 'org.jdrupes.vmoperator.java-doc-conventions'
|
||||||
id 'eclipse'
|
id 'eclipse'
|
||||||
id "com.github.node-gradle.node" version "7.0.1"
|
id "com.github.node-gradle.node" version "7.0.1"
|
||||||
|
|
@ -18,7 +19,7 @@ allprojects {
|
||||||
}
|
}
|
||||||
|
|
||||||
task stage {
|
task stage {
|
||||||
description = 'To be executed by CI, build and update JavaDoc.'
|
description = 'To be executed by CI.'
|
||||||
group = 'build'
|
group = 'build'
|
||||||
|
|
||||||
// Build everything first
|
// Build everything first
|
||||||
|
|
@ -26,11 +27,6 @@ task stage {
|
||||||
dependsOn subprojects.tasks.collect {
|
dependsOn subprojects.tasks.collect {
|
||||||
tc -> tc.findByName("build") }.flatten()
|
tc -> tc.findByName("build") }.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JavaVersion.current() == JavaVersion.VERSION_17) {
|
|
||||||
// Publish JavaDoc
|
|
||||||
dependsOn gitPublishPush
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
eclipse {
|
eclipse {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
|
#
|
||||||
|
#Wed Oct 02 14:48:43 CEST 2024
|
||||||
eclipse.preferences.version=1
|
eclipse.preferences.version=1
|
||||||
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
|
|
||||||
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
|
|
||||||
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
|
|
||||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||||
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
|
|
||||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
|
||||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||||
org.eclipse.jdt.core.compiler.compliance=21
|
org.eclipse.jdt.core.compiler.compliance=21
|
||||||
|
|
@ -11,12 +9,5 @@ org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||||
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
|
|
||||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||||
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
|
|
||||||
org.eclipse.jdt.core.compiler.problem.nullReference=warning
|
|
||||||
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
|
|
||||||
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
|
|
||||||
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
|
|
||||||
org.eclipse.jdt.core.compiler.release=disabled
|
|
||||||
org.eclipse.jdt.core.compiler.source=21
|
org.eclipse.jdt.core.compiler.source=21
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
eclipse.preferences.version=1
|
eclipse.preferences.version=1
|
||||||
groovy.compiler.level=40
|
groovy.compiler.level=-1
|
||||||
groovy.script.filters=**/*.dsld,y,**/*.gradle,n
|
groovy.script.filters=**/*.dsld,y,**/*.gradle,n
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,3 @@
|
||||||
/*
|
|
||||||
* This file was generated by the Gradle 'init' task.
|
|
||||||
*
|
|
||||||
* This project uses @Incubating APIs which are subject to change.
|
|
||||||
*/
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
// Support convention plugins written in Groovy. Convention plugins
|
// Support convention plugins written in Groovy. Convention plugins
|
||||||
// are build scripts in 'src/main' that automatically become available
|
// are build scripts in 'src/main' that automatically become available
|
||||||
|
|
@ -14,52 +8,24 @@ plugins {
|
||||||
id 'eclipse'
|
id 'eclipse'
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
|
||||||
// Use the plugin portal to apply community plugins in convention plugins.
|
|
||||||
gradlePluginPortal()
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main {
|
main {
|
||||||
groovy {
|
groovy {
|
||||||
srcDirs = ['src']
|
srcDirs = ['src']
|
||||||
}
|
}
|
||||||
}
|
resources {
|
||||||
|
srcDirs = ['resources']
|
||||||
test {
|
}
|
||||||
groovy {
|
}
|
||||||
srcDirs = ['test']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
eclipse {
|
eclipse {
|
||||||
|
|
||||||
project {
|
|
||||||
file {
|
|
||||||
// closure executed after .project content is loaded from existing file
|
|
||||||
// and before gradle build information is merged
|
|
||||||
beforeMerged { project ->
|
|
||||||
project.natures.clear()
|
|
||||||
project.buildCommands.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
project.natures += 'org.eclipse.buildship.core.gradleprojectnature'
|
|
||||||
// Don't build, result not used by Eclipse anyway
|
|
||||||
// project.buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
classpath {
|
|
||||||
downloadJavadoc = true
|
|
||||||
downloadSources = true
|
|
||||||
}
|
|
||||||
|
|
||||||
jdt {
|
jdt {
|
||||||
file {
|
file {
|
||||||
withProperties { properties ->
|
withProperties { properties ->
|
||||||
def formatterPrefs = new Properties()
|
def formatterPrefs = new Properties()
|
||||||
rootProject.file("gradle/org.eclipse.jdt.core.formatter.prefs")
|
rootProject.file("../gradle/org.eclipse.jdt.core.formatter.prefs")
|
||||||
.withInputStream { formatterPrefs.load(it) }
|
.withInputStream { formatterPrefs.load(it) }
|
||||||
properties.putAll(formatterPrefs)
|
properties.putAll(formatterPrefs)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
/*
|
|
||||||
* This file was generated by the Gradle 'init' task.
|
|
||||||
*
|
|
||||||
* This settings file is used to specify which projects to include in your build-logic build.
|
|
||||||
*/
|
|
||||||
|
|
||||||
rootProject.name = 'buildSrc'
|
|
||||||
|
|
@ -5,6 +5,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
|
// Apply the common versioning conventions.
|
||||||
|
// Put this at the start, because accessing project.version before
|
||||||
|
// this is applied makes things fail.
|
||||||
|
id 'org.jdrupes.vmoperator.versioning-conventions'
|
||||||
|
|
||||||
// Apply the java Plugin to add support for Java.
|
// Apply the java Plugin to add support for Java.
|
||||||
id 'java'
|
id 'java'
|
||||||
|
|
||||||
|
|
@ -13,9 +18,6 @@ plugins {
|
||||||
|
|
||||||
// Access to git information
|
// Access to git information
|
||||||
id 'org.ajoberstar.grgit'
|
id 'org.ajoberstar.grgit'
|
||||||
|
|
||||||
// Apply the common versioning conventions.
|
|
||||||
id 'org.jdrupes.vmoperator.versioning-conventions'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|
@ -53,21 +55,25 @@ sourceSets {
|
||||||
|
|
||||||
java {
|
java {
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(17)
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
manifest {
|
manifest {
|
||||||
inputs.property("gitDescriptor", { grgit.describe(always: true) })
|
def matchExpr = [ project.tagName + "*" ]
|
||||||
|
|
||||||
|
inputs.property("gitDescriptor",
|
||||||
|
{ grgit.describe(always: true, match: matchExpr) })
|
||||||
|
|
||||||
// Set Git revision information in the manifests of built bundles
|
// Set Git revision information in the manifests of built bundles
|
||||||
|
def gitDesc = grgit.describe(always: true, match: matchExpr)
|
||||||
attributes([
|
attributes([
|
||||||
"Implementation-Title": project.name,
|
"Implementation-Title": project.name,
|
||||||
"Implementation-Version": "$project.version (built from ${grgit.describe(always: true)})",
|
"Implementation-Version": "$project.version (built from ${gitDesc})",
|
||||||
"Implementation-Vendor": grgit.repository.jgit.repository.config.getString("user", null, "name")
|
"Implementation-Vendor": grgit.repository.jgit.repository.config.getString("user", null, "name")
|
||||||
+ " (" + grgit.repository.jgit.repository.config.getString("user", null, "email") + ")",
|
+ " (" + grgit.repository.jgit.repository.config.getString("user", null, "email") + ")",
|
||||||
"Git-Descriptor": grgit.describe(always: true),
|
"Git-Descriptor": gitDesc,
|
||||||
"Git-SHA": grgit.head().id,
|
"Git-SHA": grgit.head().id,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,31 +22,28 @@ configurations {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
markdownDoclet "org.jdrupes.mdoclet:doclet:3.1.0"
|
markdownDoclet "org.jdrupes.mdoclet:doclet:4.0.0"
|
||||||
javadocTaglets "org.jdrupes.taglets:plantuml-taglet:2.1.0"
|
javadocTaglets "org.jdrupes.taglets:plantuml-taglet:3.0.0"
|
||||||
}
|
|
||||||
|
|
||||||
task javadocResources(type: Copy) {
|
|
||||||
into file(docDestinationDir)
|
|
||||||
from ("${rootProject.rootDir}/misc") {
|
|
||||||
include '*.woff2'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task apidocs (type: JavaExec) {
|
task apidocs (type: JavaExec) {
|
||||||
// Does not work on JitPack, no /usr/bin/dot
|
// Does not work on JitPack, no /usr/bin/dot
|
||||||
enabled = JavaVersion.current() == JavaVersion.VERSION_17
|
enabled = JavaVersion.current() == JavaVersion.VERSION_21
|
||||||
|
|
||||||
dependsOn javadocResources
|
|
||||||
|
|
||||||
outputs.dir(docDestinationDir)
|
outputs.dir(docDestinationDir)
|
||||||
|
|
||||||
inputs.file rootProject.file('overview.md')
|
inputs.file rootProject.file('overview.md')
|
||||||
inputs.file "${rootProject.rootDir}/misc/stylesheet.css"
|
inputs.file "${rootProject.rootDir}/misc/javadoc-overwrites.css"
|
||||||
|
|
||||||
jvmArgs = ['--add-exports=jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
|
jvmArgs = ['--add-exports=jdk.compiler/com.sun.tools.doclint=ALL-UNNAMED',
|
||||||
'--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED']
|
'--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
|
||||||
main = 'jdk.javadoc.internal.tool.Main'
|
'--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
|
||||||
|
'--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
|
||||||
|
'--add-exports=jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
|
||||||
|
'--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit=ALL-UNNAMED',
|
||||||
|
'--add-opens=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit.resources.releases=ALL-UNNAMED',
|
||||||
|
'-Duser.language=en', '-Duser.region=US']
|
||||||
|
mainClass = 'jdk.javadoc.internal.tool.Main'
|
||||||
|
|
||||||
gradle.projectsEvaluated {
|
gradle.projectsEvaluated {
|
||||||
// Make sure that other projects' compileClasspaths are resolved
|
// Make sure that other projects' compileClasspaths are resolved
|
||||||
|
|
@ -69,8 +66,8 @@ task apidocs (type: JavaExec) {
|
||||||
'-package',
|
'-package',
|
||||||
'-use',
|
'-use',
|
||||||
'-linksource',
|
'-linksource',
|
||||||
'-link', 'https://docs.oracle.com/en/java/javase/17/docs/api/',
|
'-link', 'https://docs.oracle.com/en/java/javase/21/docs/api/',
|
||||||
'-link', 'https://mnlipp.github.io/jgrapes/latest-release/javadoc/',
|
'-link', 'https://jgrapes.org/latest-release/javadoc/',
|
||||||
'-link', 'https://freemarker.apache.org/docs/api/',
|
'-link', 'https://freemarker.apache.org/docs/api/',
|
||||||
'--add-exports', 'jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
|
'--add-exports', 'jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
|
||||||
'--add-exports', 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
|
'--add-exports', 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
|
||||||
|
|
@ -88,7 +85,7 @@ task apidocs (type: JavaExec) {
|
||||||
'-bottom', rootProject.file("misc/javadoc.bottom.txt").text,
|
'-bottom', rootProject.file("misc/javadoc.bottom.txt").text,
|
||||||
'--allow-script-in-comments',
|
'--allow-script-in-comments',
|
||||||
'-Xdoclint:-html',
|
'-Xdoclint:-html',
|
||||||
'--main-stylesheet', "${rootProject.rootDir}/misc/stylesheet.css",
|
'--add-stylesheet', "${rootProject.rootDir}/misc/javadoc-overwrites.css",
|
||||||
'--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.formats.html=ALL-UNNAMED',
|
'--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.formats.html=ALL-UNNAMED',
|
||||||
'-quiet'
|
'-quiet'
|
||||||
]
|
]
|
||||||
|
|
@ -97,34 +94,27 @@ task apidocs (type: JavaExec) {
|
||||||
ignoreExitValue true
|
ignoreExitValue true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task testJavadoc(type: Javadoc) {
|
||||||
|
enabled = JavaVersion.current() == JavaVersion.VERSION_21
|
||||||
|
|
||||||
|
source = fileTree(dir: 'testfiles', include: '**/*.java')
|
||||||
|
destinationDir = project.file("build/testfiles-gradle")
|
||||||
|
options.docletpath = configurations.markdownDoclet.files.asType(List)
|
||||||
|
options.doclet = 'org.jdrupes.mdoclet.MDoclet'
|
||||||
|
options.overview = 'testfiles/overview.md'
|
||||||
|
options.addStringOption('Xdoclint:-html', '-quiet')
|
||||||
|
|
||||||
|
options.setJFlags([
|
||||||
|
'--add-exports=jdk.compiler/com.sun.tools.doclint=ALL-UNNAMED',
|
||||||
|
'--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
|
||||||
|
'--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
|
||||||
|
'--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
|
||||||
|
'--add-exports=jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
|
||||||
|
'--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit=ALL-UNNAMED',
|
||||||
|
'--add-opens=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit.resources.releases=ALL-UNNAMED'])
|
||||||
|
}
|
||||||
// Prepare github authentication for plugins
|
// Prepare github authentication for plugins
|
||||||
if (System.properties['org.ajoberstar.grgit.auth.username'] == null) {
|
if (System.properties['org.ajoberstar.grgit.auth.username'] == null) {
|
||||||
System.setProperty('org.ajoberstar.grgit.auth.username',
|
System.setProperty('org.ajoberstar.grgit.auth.username',
|
||||||
project.rootProject.properties['repo.access.token'] ?: "nouser")
|
project.rootProject.properties['website.push.token'] ?: "nouser")
|
||||||
}
|
|
||||||
|
|
||||||
gitPublish {
|
|
||||||
repoUri = 'https://github.com/mnlipp/VM-Operator.git'
|
|
||||||
branch = 'gh-pages'
|
|
||||||
contents {
|
|
||||||
from("${rootProject.buildDir}/javadoc") {
|
|
||||||
into 'javadoc'
|
|
||||||
}
|
|
||||||
if (!findProject(':org.jdrupes.vmoperator.runner.qemu').isSnapshot
|
|
||||||
&& !findProject(':org.jdrupes.vmoperator.manager').isSnapshot) {
|
|
||||||
from("${rootProject.buildDir}/javadoc") {
|
|
||||||
into 'latest-release/javadoc'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preserve { include '**/*' }
|
|
||||||
commitMessage = "Updated."
|
|
||||||
}
|
|
||||||
|
|
||||||
gradle.projectsEvaluated {
|
|
||||||
tasks.gitPublishReset.mustRunAfter subprojects.tasks
|
|
||||||
.collect { tc -> tc.findByName("build") }.flatten()
|
|
||||||
tasks.gitPublishReset.mustRunAfter subprojects.tasks
|
|
||||||
.collect { tc -> tc.findByName("test") }.flatten()
|
|
||||||
tasks.gitPublishCopy.dependsOn apidocs
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,21 +11,26 @@ plugins {
|
||||||
id 'pl.allegro.tech.build.axion-release'
|
id 'pl.allegro.tech.build.axion-release'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def shortened = project.name.startsWith(project.group + ".") ?
|
||||||
|
project.name.substring(project.group.length() + 1) : project.name
|
||||||
|
if (shortened == "manager") {
|
||||||
|
shortened = "manager-app";
|
||||||
|
}
|
||||||
|
var tagName = shortened.replace('.', '-') + "-"
|
||||||
|
if (grgit.branch.current.name != "main"
|
||||||
|
&& grgit.branch.current.name != "HEAD"
|
||||||
|
&& !grgit.branch.current.name.startsWith("testing")
|
||||||
|
&& !grgit.branch.current.name.startsWith("release")
|
||||||
|
&& !grgit.branch.current.name.startsWith("develop")) {
|
||||||
|
tagName = tagName + grgit.branch.current.name.replace('/', '-') + "-"
|
||||||
|
}
|
||||||
|
project.ext.tagName = tagName
|
||||||
|
|
||||||
scmVersion {
|
scmVersion {
|
||||||
versionIncrementer 'incrementMinor'
|
versionIncrementer 'incrementMinor'
|
||||||
tag {
|
tag {
|
||||||
def shortened = project.name.startsWith(project.group + ".") ?
|
prefix = project.tagName
|
||||||
project.name.substring(project.group.length() + 1) : project.name
|
|
||||||
if (shortened == "manager") {
|
|
||||||
shortened = "manager-app";
|
|
||||||
}
|
|
||||||
var p = shortened.replace('.', '-') + "-"
|
|
||||||
if (grgit.branch.current.name != "main"
|
|
||||||
&& !grgit.branch.current.name.startsWith("release")) {
|
|
||||||
p = p + grgit.branch.current.name.replace('/', '-') + "-"
|
|
||||||
}
|
|
||||||
prefix = p
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
version = scmVersion.version
|
project.version = scmVersion.version
|
||||||
ext.isSnapshot = version.endsWith('-SNAPSHOT')
|
ext.isSnapshot = version.endsWith('-SNAPSHOT')
|
||||||
|
|
|
||||||
74
deploy/crds/vmpools-crd.yaml
Normal file
74
deploy/crds/vmpools-crd.yaml
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: vmpools.vmoperator.jdrupes.org
|
||||||
|
spec:
|
||||||
|
group: vmoperator.jdrupes.org
|
||||||
|
# list of versions supported by this CustomResourceDefinition
|
||||||
|
versions:
|
||||||
|
- name: v1
|
||||||
|
served: true
|
||||||
|
storage: true
|
||||||
|
schema:
|
||||||
|
openAPIV3Schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
spec:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
retention:
|
||||||
|
description: >-
|
||||||
|
Defines the timeout for assignments. The time may be
|
||||||
|
specified as ISO 8601 time or duration. When specifying
|
||||||
|
a duration, it will be added to the last time the VM's
|
||||||
|
console was used to obtain the timeout.
|
||||||
|
type: string
|
||||||
|
pattern: '^(?:\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d{1,9})?(?:Z|[+-](?:[01]\d|2[0-3])(?:|:?[0-5]\d))|P(?:\d+Y)?(?:\d+M)?(?:\d+W)?(?:\d+D)?(?:T(?:\d+[Hh])?(?:\d+[Mm])?(?:\d+(?:\.\d{1,9})?[Ss])?)?)$'
|
||||||
|
default: "PT1h"
|
||||||
|
loginOnAssignment:
|
||||||
|
description: >-
|
||||||
|
If set to true, the user will be automatically logged in
|
||||||
|
to the VM's console when the VM is assigned to him.
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
permissions:
|
||||||
|
type: array
|
||||||
|
description: >-
|
||||||
|
Defines permissions for accessing and manipulating the Pool.
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
description: >-
|
||||||
|
Permissions can be granted to a user or to a role.
|
||||||
|
oneOf:
|
||||||
|
- required:
|
||||||
|
- user
|
||||||
|
- required:
|
||||||
|
- role
|
||||||
|
properties:
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
|
role:
|
||||||
|
type: string
|
||||||
|
may:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- start
|
||||||
|
- stop
|
||||||
|
- reset
|
||||||
|
- accessConsole
|
||||||
|
- "*"
|
||||||
|
default: ["accessConsole"]
|
||||||
|
required:
|
||||||
|
- permissions
|
||||||
|
# either Namespaced or Cluster
|
||||||
|
scope: Namespaced
|
||||||
|
names:
|
||||||
|
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
|
||||||
|
plural: vmpools
|
||||||
|
# singular name to be used as an alias on the CLI and for display
|
||||||
|
singular: vmpool
|
||||||
|
# kind is normally the CamelCased singular type. Your resource manifests use this.
|
||||||
|
kind: VmPool
|
||||||
|
listKind: VmPoolList
|
||||||
|
|
@ -994,6 +994,10 @@ spec:
|
||||||
type: array
|
type: array
|
||||||
description: >-
|
description: >-
|
||||||
Defines permissions for accessing and manipulating the VM.
|
Defines permissions for accessing and manipulating the VM.
|
||||||
|
The meaning of most permissions should be obvious. The
|
||||||
|
difference between "accessConsole" and "takeConsole" is
|
||||||
|
that "takeConsole" allows the user to take control of
|
||||||
|
the console even if it is already in use by another user.
|
||||||
items:
|
items:
|
||||||
type: object
|
type: object
|
||||||
description: >-
|
description: >-
|
||||||
|
|
@ -1012,8 +1016,26 @@ spec:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
enum: ["start", "stop", "accessConsole", "*"]
|
enum:
|
||||||
|
- start
|
||||||
|
- stop
|
||||||
|
- reset
|
||||||
|
- accessConsole
|
||||||
|
- takeConsole
|
||||||
|
- "*"
|
||||||
default: []
|
default: []
|
||||||
|
pools:
|
||||||
|
type: array
|
||||||
|
description: >-
|
||||||
|
List of pools this VM belongs to.
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
default: []
|
||||||
|
loggingProperties:
|
||||||
|
type: string
|
||||||
|
description: >-
|
||||||
|
Override the default logging properties for
|
||||||
|
the runner for this VM.
|
||||||
vm:
|
vm:
|
||||||
type: object
|
type: object
|
||||||
description: Defines the VM.
|
description: Defines the VM.
|
||||||
|
|
@ -1405,6 +1427,15 @@ spec:
|
||||||
display:
|
display:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
outputs:
|
||||||
|
type: integer
|
||||||
|
default: 1
|
||||||
|
loggedInUser:
|
||||||
|
description: >-
|
||||||
|
The name of a user that should be automatically
|
||||||
|
logged in on the display. Note that this requires
|
||||||
|
support from an agent in the guest OS.
|
||||||
|
type: string
|
||||||
spice:
|
spice:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -1439,6 +1470,10 @@ spec:
|
||||||
type: object
|
type: object
|
||||||
default: {}
|
default: {}
|
||||||
properties:
|
properties:
|
||||||
|
runnerVersion:
|
||||||
|
description: >-
|
||||||
|
The version string of the runner.
|
||||||
|
type: string
|
||||||
cpus:
|
cpus:
|
||||||
description: >-
|
description: >-
|
||||||
Number of CPUs currently in use.
|
Number of CPUs currently in use.
|
||||||
|
|
@ -1449,12 +1484,50 @@ spec:
|
||||||
Amount of memory in use.
|
Amount of memory in use.
|
||||||
type: string
|
type: string
|
||||||
default: "0"
|
default: "0"
|
||||||
|
consoleClient:
|
||||||
|
description: >-
|
||||||
|
The hostname of the currently connected client.
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
consoleUser:
|
||||||
|
description: >-
|
||||||
|
The id of the user who has last requested a console
|
||||||
|
connection.
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
loggedInUser:
|
||||||
|
description: >-
|
||||||
|
The name of a user that is currently logged in by the
|
||||||
|
VM operator agent.
|
||||||
|
type: string
|
||||||
displayPasswordSerial:
|
displayPasswordSerial:
|
||||||
description: >-
|
description: >-
|
||||||
Counts changes of the display password. Set to -1
|
Counts changes of the display password. Set to -1
|
||||||
by the runner if password protection is not enabled.
|
by the runner if password protection is not enabled.
|
||||||
type: integer
|
type: integer
|
||||||
default: 0
|
default: 0
|
||||||
|
osinfo:
|
||||||
|
description: Copy of the OS info provided by the guest agent.
|
||||||
|
type: object
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
assignment:
|
||||||
|
description: >-
|
||||||
|
The assignment of this VM to a a particular user.
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
pool:
|
||||||
|
description: >-
|
||||||
|
The pool this VM is taken from.
|
||||||
|
type: string
|
||||||
|
user:
|
||||||
|
description: >-
|
||||||
|
The user this VM is assigned to.
|
||||||
|
type: string
|
||||||
|
lastUsed:
|
||||||
|
description: >-
|
||||||
|
The last time this VM was used by the user.
|
||||||
|
type: string
|
||||||
|
default: {}
|
||||||
conditions:
|
conditions:
|
||||||
description: >-
|
description: >-
|
||||||
List of component conditions observed
|
List of component conditions observed
|
||||||
|
|
@ -1465,6 +1538,30 @@ spec:
|
||||||
lastTransitionTime: "1970-01-01T00:00:00Z"
|
lastTransitionTime: "1970-01-01T00:00:00Z"
|
||||||
reason: Creation
|
reason: Creation
|
||||||
message: "Creation of CR"
|
message: "Creation of CR"
|
||||||
|
- type: Booted
|
||||||
|
status: "False"
|
||||||
|
observedGeneration: 1
|
||||||
|
lastTransitionTime: "1970-01-01T00:00:00Z"
|
||||||
|
reason: Creation
|
||||||
|
message: "Creation of CR"
|
||||||
|
- type: VmopAgentConnected
|
||||||
|
status: "False"
|
||||||
|
observedGeneration: 1
|
||||||
|
lastTransitionTime: "1970-01-01T00:00:00Z"
|
||||||
|
reason: Creation
|
||||||
|
message: "Creation of CR"
|
||||||
|
- type: UserLoggedIn
|
||||||
|
status: "False"
|
||||||
|
observedGeneration: 1
|
||||||
|
lastTransitionTime: "1970-01-01T00:00:00Z"
|
||||||
|
reason: Creation
|
||||||
|
message: "Creation of CR"
|
||||||
|
- type: ConsoleConnected
|
||||||
|
status: "False"
|
||||||
|
observedGeneration: 1
|
||||||
|
lastTransitionTime: "1970-01-01T00:00:00Z"
|
||||||
|
reason: Creation
|
||||||
|
message: "Creation of CR"
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: object
|
type: object
|
||||||
|
|
|
||||||
|
|
@ -21,22 +21,31 @@ spec:
|
||||||
- name: vm-operator
|
- name: vm-operator
|
||||||
image: >-
|
image: >-
|
||||||
ghcr.io/mnlipp/org.jdrupes.vmoperator.manager:latest
|
ghcr.io/mnlipp/org.jdrupes.vmoperator.manager:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
env:
|
||||||
|
- name: JAVA_OPTS
|
||||||
|
# The VM operator needs about 25 MB of memory, plus 1 MB for
|
||||||
|
# each VM. The reason is that for the sake of effeciency, we
|
||||||
|
# have to keep a parsed representation of the CRD in memory,
|
||||||
|
# which requires about 512 KB per VM. While handling updates,
|
||||||
|
# we temporarily have the old and the new version of the CRD
|
||||||
|
# in memory, so we need another 512 KB per VM.
|
||||||
|
value: "-Xmx128m"
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: config
|
- name: config
|
||||||
mountPath: /etc/opt/vmoperator
|
mountPath: /etc/opt/vmoperator
|
||||||
- name: vmop-image-repository
|
- name: vmop-image-repository
|
||||||
mountPath: /var/local/vmop-image-repository
|
mountPath: /var/local/vmop-image-repository
|
||||||
imagePullPolicy: Always
|
|
||||||
securityContext:
|
securityContext:
|
||||||
capabilities:
|
capabilities:
|
||||||
drop:
|
drop:
|
||||||
- ALL
|
- ALL
|
||||||
readOnlyRootFilesystem: true
|
readOnlyRootFilesystem: true
|
||||||
allowPrivilegeEscalation: false
|
allowPrivilegeEscalation: false
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 100m
|
|
||||||
memory: 128Mi
|
|
||||||
volumes:
|
volumes:
|
||||||
- name: config
|
- name: config
|
||||||
configMap:
|
configMap:
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,15 @@ rules:
|
||||||
- vmoperator.jdrupes.org
|
- vmoperator.jdrupes.org
|
||||||
resources:
|
resources:
|
||||||
- vms
|
- vms
|
||||||
|
- vmpools
|
||||||
verbs:
|
verbs:
|
||||||
- '*'
|
- '*'
|
||||||
|
- apiGroups:
|
||||||
|
- vmoperator.jdrupes.org
|
||||||
|
resources:
|
||||||
|
- vms/status
|
||||||
|
verbs:
|
||||||
|
- patch
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- apps
|
- apps
|
||||||
resources:
|
resources:
|
||||||
|
|
@ -28,9 +35,12 @@ rules:
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- ""
|
- ""
|
||||||
resources:
|
resources:
|
||||||
|
- persistentvolumeclaims
|
||||||
- pods
|
- pods
|
||||||
verbs:
|
verbs:
|
||||||
|
- watch
|
||||||
- list
|
- list
|
||||||
- get
|
- get
|
||||||
|
- create
|
||||||
- delete
|
- delete
|
||||||
- patch
|
- patch
|
||||||
|
|
|
||||||
3
dev-example/.gitignore
vendored
3
dev-example/.gitignore
vendored
|
|
@ -1 +1,4 @@
|
||||||
/test-vm-ci.yaml
|
/test-vm-ci.yaml
|
||||||
|
/kubeconfig.yaml
|
||||||
|
/crds/
|
||||||
|
/.vm-operator-cmd.rc
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@
|
||||||
The CRD must be deployed independently. Apart from that, the
|
The CRD must be deployed independently. Apart from that, the
|
||||||
`kustomize.yaml`
|
`kustomize.yaml`
|
||||||
|
|
||||||
* creates a small cdrom image repository and
|
* creates a small cdrom image repository and
|
||||||
|
|
||||||
* deploys the operator in namespace `vmop-dev` with a replica of 0.
|
* deploys the operator in namespace `vmop-dev` with a replica of 0.
|
||||||
|
|
||||||
This allows you to run the manager in your IDE.
|
This allows you to run the manager in your IDE.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,28 @@
|
||||||
"/Controller":
|
"/Controller":
|
||||||
namespace: vmop-dev
|
namespace: vmop-dev
|
||||||
"/Reconciler":
|
"/Reconciler":
|
||||||
runnerData:
|
runnerDataPvc:
|
||||||
storageClassName: null
|
storageClassName: rook-cephfs
|
||||||
|
loadBalancerService:
|
||||||
|
labels:
|
||||||
|
label1: label1
|
||||||
|
label2: toBeReplaced
|
||||||
|
annotations:
|
||||||
|
metallb.universe.tf/loadBalancerIPs: 192.168.168.1
|
||||||
|
metallb.universe.tf/ip-allocated-from-pool: single-common
|
||||||
|
metallb.universe.tf/allow-shared-ip: single-common
|
||||||
|
loggingProperties: |
|
||||||
|
# Defaults for namespace (VM domain)
|
||||||
|
handlers=java.util.logging.ConsoleHandler
|
||||||
|
|
||||||
|
#org.jgrapes.level=FINE
|
||||||
|
#org.jgrapes.core.handlerTracking.level=FINER
|
||||||
|
|
||||||
|
org.jdrupes.vmoperator.runner.qemu.level=FINEST
|
||||||
|
|
||||||
|
java.util.logging.ConsoleHandler.level=ALL
|
||||||
|
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
|
||||||
|
java.util.logging.SimpleFormatter.format=%1$tb %1$td %1$tT %4$s %5$s%6$s%n
|
||||||
"/GuiSocketServer":
|
"/GuiSocketServer":
|
||||||
port: 8888
|
port: 8888
|
||||||
"/GuiHttpServer":
|
"/GuiHttpServer":
|
||||||
|
|
@ -17,18 +37,33 @@
|
||||||
"/WebConsole":
|
"/WebConsole":
|
||||||
"/LoginConlet":
|
"/LoginConlet":
|
||||||
users:
|
users:
|
||||||
- name: admin
|
- name: admin
|
||||||
fullName: Administrator
|
fullName: Administrator
|
||||||
password: "$2b$05$NiBd74ZGdplLC63ePZf1f.UtjMKkbQ23cQoO2OKOFalDBHWAOy21."
|
password: "$2b$05$NiBd74ZGdplLC63ePZf1f.UtjMKkbQ23cQoO2OKOFalDBHWAOy21."
|
||||||
- name: test
|
- name: operator
|
||||||
fullName: Test Account
|
fullName: Operator
|
||||||
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
|
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
|
||||||
|
- name: test1
|
||||||
|
fullName: Test Account 1
|
||||||
|
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
|
||||||
|
- name: test2
|
||||||
|
fullName: Test Account 2
|
||||||
|
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
|
||||||
|
- name: test3
|
||||||
|
fullName: Test Account 3
|
||||||
|
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
|
||||||
"/RoleConfigurator":
|
"/RoleConfigurator":
|
||||||
rolesByUser:
|
rolesByUser:
|
||||||
# User admin has role admin
|
# User admin has role admin
|
||||||
admin:
|
admin:
|
||||||
- admin
|
- admin
|
||||||
test:
|
operator:
|
||||||
|
- operator
|
||||||
|
test1:
|
||||||
|
- user
|
||||||
|
test2:
|
||||||
|
- user
|
||||||
|
test3:
|
||||||
- user
|
- user
|
||||||
# All users have role other
|
# All users have role other
|
||||||
"*":
|
"*":
|
||||||
|
|
@ -39,13 +74,16 @@
|
||||||
# Admins can use all conlets
|
# Admins can use all conlets
|
||||||
admin:
|
admin:
|
||||||
- "*"
|
- "*"
|
||||||
|
operator:
|
||||||
|
- org.jdrupes.vmoperator.vmmgmt.VmMgmt
|
||||||
|
- org.jdrupes.vmoperator.vmaccess.VmAccess
|
||||||
user:
|
user:
|
||||||
- org.jdrupes.vmoperator.vmviewer.VmViewer
|
- org.jdrupes.vmoperator.vmaccess.VmAccess
|
||||||
# Others cannot use any conlet (except login conlet to log out)
|
# Others cannot use any conlet (except login conlet to log out)
|
||||||
other:
|
other:
|
||||||
- org.jgrapes.webconlet.oidclogin.LoginConlet
|
- org.jgrapes.webconlet.oidclogin.LoginConlet
|
||||||
"/ComponentCollector":
|
"/ComponentCollector":
|
||||||
"/VmViewer":
|
"/VmAccess":
|
||||||
displayResource:
|
displayResource:
|
||||||
preferredIpVersion: ipv4
|
preferredIpVersion: ipv4
|
||||||
syncPreviewsFor:
|
syncPreviewsFor:
|
||||||
|
|
|
||||||
47
dev-example/gen-pool-vm-crds
Executable file
47
dev-example/gen-pool-vm-crds
Executable file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
function usage() {
|
||||||
|
cat >&2 <<EOF
|
||||||
|
Usage: $0 [OPTION]... [TEMPLATE]
|
||||||
|
Generate VM CRDs using TEMPLATE.
|
||||||
|
|
||||||
|
-c, --count Count of VMs to generate
|
||||||
|
-d, --destination DIR Generate into given directory (default: ".")
|
||||||
|
-h, --help Print this help
|
||||||
|
-p, --prefix PREFIX Prefix for generated file (default: basename of template)
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
count=0
|
||||||
|
destination=.
|
||||||
|
template=""
|
||||||
|
prefix=""
|
||||||
|
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-c|--count) shift; count=$1;;
|
||||||
|
-d|--destination) shift; destination="$1";;
|
||||||
|
-h|--help) shift; usage;;
|
||||||
|
-p|--prefix) shift; prefix="$1";;
|
||||||
|
-*) echo >&2 "Unknown option: $1"; exit 1;;
|
||||||
|
*) template="$1";;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$template" ]; then
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$count" = "0" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
for number in $(seq 1 $count); do
|
||||||
|
if [ -z "$prefix" ]; then
|
||||||
|
prefix=$(basename $template .tpl.yaml)
|
||||||
|
fi
|
||||||
|
name="$prefix$(printf %03d $number)"
|
||||||
|
index=$(($number - 1))
|
||||||
|
esh -o $destination/$name.yaml $template number=$number index=$index
|
||||||
|
done
|
||||||
|
|
@ -35,6 +35,14 @@ patches:
|
||||||
"/Reconciler":
|
"/Reconciler":
|
||||||
runnerData:
|
runnerData:
|
||||||
storageClassName: null
|
storageClassName: null
|
||||||
|
loadBalancerService:
|
||||||
|
labels:
|
||||||
|
label1: label1
|
||||||
|
label2: toBeReplaced
|
||||||
|
annotations:
|
||||||
|
metallb.universe.tf/loadBalancerIPs: 192.168.168.1
|
||||||
|
metallb.universe.tf/ip-allocated-from-pool: single-common
|
||||||
|
metallb.universe.tf/allow-shared-ip: single-common
|
||||||
"/GuiSocketServer":
|
"/GuiSocketServer":
|
||||||
port: 8888
|
port: 8888
|
||||||
"/GuiHttpServer":
|
"/GuiHttpServer":
|
||||||
|
|
@ -43,18 +51,28 @@ patches:
|
||||||
"/WebConsole":
|
"/WebConsole":
|
||||||
"/LoginConlet":
|
"/LoginConlet":
|
||||||
users:
|
users:
|
||||||
admin:
|
- name: admin
|
||||||
fullName: Administrator
|
fullName: Administrator
|
||||||
password: "$2b$05$NiBd74ZGdplLC63ePZf1f.UtjMKkbQ23cQoO2OKOFalDBHWAOy21."
|
password: "$2b$05$NiBd74ZGdplLC63ePZf1f.UtjMKkbQ23cQoO2OKOFalDBHWAOy21."
|
||||||
test:
|
- name: test1
|
||||||
fullName: Test Account
|
fullName: Test Account
|
||||||
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
|
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
|
||||||
|
- name: test2
|
||||||
|
fullName: Test Account
|
||||||
|
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
|
||||||
|
- name: test3
|
||||||
|
fullName: Test Account
|
||||||
|
password: "$2b$05$hZaI/jToXf/d3BctZdT38Or7H7h6Pn2W3WiB49p5AyhDHFkkYCvo2"
|
||||||
"/RoleConfigurator":
|
"/RoleConfigurator":
|
||||||
rolesByUser:
|
rolesByUser:
|
||||||
# User admin has role admin
|
# User admin has role admin
|
||||||
admin:
|
admin:
|
||||||
- admin
|
- admin
|
||||||
test:
|
test1:
|
||||||
|
- user
|
||||||
|
test2:
|
||||||
|
- user
|
||||||
|
test3:
|
||||||
- user
|
- user
|
||||||
# All users have role other
|
# All users have role other
|
||||||
"*":
|
"*":
|
||||||
|
|
@ -71,7 +89,7 @@ patches:
|
||||||
other:
|
other:
|
||||||
- org.jgrapes.webconlet.locallogin.LoginConlet
|
- org.jgrapes.webconlet.locallogin.LoginConlet
|
||||||
"/ComponentCollector":
|
"/ComponentCollector":
|
||||||
"/VmViewer":
|
"/VmAccess":
|
||||||
displayResource:
|
displayResource:
|
||||||
preferredIpVersion: ipv4
|
preferredIpVersion: ipv4
|
||||||
syncPreviewsFor:
|
syncPreviewsFor:
|
||||||
|
|
|
||||||
66
dev-example/pool-action
Executable file
66
dev-example/pool-action
Executable file
|
|
@ -0,0 +1,66 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
function usage() {
|
||||||
|
cat >&2 <<EOF
|
||||||
|
Usage: $0 pool-name action
|
||||||
|
Applys action to all VMs in the pool.
|
||||||
|
|
||||||
|
--context Context to be passed to kubectl (required)
|
||||||
|
-n, --namespace Namespace to be passed to kubectl
|
||||||
|
|
||||||
|
Action is one of "start", "stop", "delete" or "delete-disks"
|
||||||
|
|
||||||
|
Defaults for context and namespace are read from .vm-operator-cmd.rc.
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
unset pool
|
||||||
|
unset action
|
||||||
|
unset context
|
||||||
|
namespace=default
|
||||||
|
|
||||||
|
if [ -r .vm-operator-cmd.rc ]; then
|
||||||
|
. .vm-operator-cmd.rc
|
||||||
|
fi
|
||||||
|
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--context) shift; context="$1";;
|
||||||
|
--context=*) IFS='=' read -r option value <<< "$1"; context="$value";;
|
||||||
|
-n|--namespace) shift; namespace="$1";;
|
||||||
|
-*) echo >&2 "Unknown option: $1"; exit 1;;
|
||||||
|
*) if [ ! -v pool ]; then
|
||||||
|
pool="$1"
|
||||||
|
elif [ ! -v action ]; then
|
||||||
|
action="$1"
|
||||||
|
else
|
||||||
|
usage
|
||||||
|
fi;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -v pool -o ! -v "action" -o ! -v context ]; then
|
||||||
|
echo >&2 "Missing arguments or context not set."
|
||||||
|
echo >&2
|
||||||
|
usage
|
||||||
|
fi
|
||||||
|
case "$action" in
|
||||||
|
"start"|"stop"|"delete"|"delete-disks") ;;
|
||||||
|
*) usage;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
kubectl --context="$context" -n "$namespace" get vms -o json \
|
||||||
|
| jq -r '.items[] | select(.spec.pools | contains(["'${pool}'"])) | .metadata.name' \
|
||||||
|
| while read vmName; do
|
||||||
|
case "$action" in
|
||||||
|
start) kubectl --context="$context" -n "$namespace" patch vms "$vmName" \
|
||||||
|
--type='merge' -p '{"spec":{"vm":{"state":"Running"}}}';;
|
||||||
|
stop) kubectl --context="$context" -n "$namespace" patch vms "$vmName" \
|
||||||
|
--type='merge' -p '{"spec":{"vm":{"state":"Stopped"}}}';;
|
||||||
|
delete) kubectl --context="$context" -n "$namespace" delete vm/"$vmName";;
|
||||||
|
delete-disks) kubectl --context="$context" -n "$namespace" delete \
|
||||||
|
pvc -l app.kubernetes.io/instance="$vmName" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
17
dev-example/test-pool.yaml
Normal file
17
dev-example/test-pool.yaml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
apiVersion: "vmoperator.jdrupes.org/v1"
|
||||||
|
kind: VmPool
|
||||||
|
metadata:
|
||||||
|
namespace: vmop-dev
|
||||||
|
name: test-vms
|
||||||
|
spec:
|
||||||
|
retention: "PT1m"
|
||||||
|
loginOnAssignment: true
|
||||||
|
permissions:
|
||||||
|
- user: admin
|
||||||
|
may:
|
||||||
|
- accessConsole
|
||||||
|
- start
|
||||||
|
- role: user
|
||||||
|
may:
|
||||||
|
- accessConsole
|
||||||
|
- start
|
||||||
10
dev-example/test-vm-snapshot.yaml
Normal file
10
dev-example/test-vm-snapshot.yaml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
apiVersion: snapshot.storage.k8s.io/v1
|
||||||
|
kind: VolumeSnapshot
|
||||||
|
metadata:
|
||||||
|
namespace: vmop-dev
|
||||||
|
name: test-vm-system-disk-snapshot
|
||||||
|
spec:
|
||||||
|
volumeSnapshotClassName: csi-rbdplugin-snapclass
|
||||||
|
source:
|
||||||
|
persistentVolumeClaimName: test-vm-system-disk
|
||||||
66
dev-example/test-vm.tpl.yaml
Normal file
66
dev-example/test-vm.tpl.yaml
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
apiVersion: "vmoperator.jdrupes.org/v1"
|
||||||
|
kind: VirtualMachine
|
||||||
|
metadata:
|
||||||
|
namespace: vmop-dev
|
||||||
|
name: test-vm<%= $(printf "%02d" ${number}) %>
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/sync-wave: "20"
|
||||||
|
|
||||||
|
spec:
|
||||||
|
image:
|
||||||
|
source: ghcr.io/mnlipp/org.jdrupes.vmoperator.runner.qemu-arch:latest
|
||||||
|
# source: registry.mnl.de/org/jdrupes/vm-operator/org.jdrupes.vmoperator.runner.qemu-arch:testing
|
||||||
|
# source: docker-registry.lan.mnl.de/vmoperator/org.jdrupes.vmoperator.runner.qemu-arch:latest
|
||||||
|
pullPolicy: Always
|
||||||
|
|
||||||
|
runnerTemplate:
|
||||||
|
update: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
- role: admin
|
||||||
|
may:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
guestShutdownStops: true
|
||||||
|
|
||||||
|
cloudInit:
|
||||||
|
metaData: {}
|
||||||
|
|
||||||
|
pools:
|
||||||
|
- test-vms
|
||||||
|
|
||||||
|
vm:
|
||||||
|
# state: Running
|
||||||
|
bootMenu: true
|
||||||
|
maximumCpus: 4
|
||||||
|
currentCpus: 2
|
||||||
|
maximumRam: 6Gi
|
||||||
|
currentRam: 4Gi
|
||||||
|
|
||||||
|
networks:
|
||||||
|
# No bridge on TC1
|
||||||
|
# - tap: {}
|
||||||
|
- user: {}
|
||||||
|
|
||||||
|
disks:
|
||||||
|
- volumeClaimTemplate:
|
||||||
|
metadata:
|
||||||
|
name: system
|
||||||
|
spec:
|
||||||
|
storageClassName: ceph-rbd3slow
|
||||||
|
dataSource:
|
||||||
|
name: test-vm-system-disk-snapshot
|
||||||
|
kind: VolumeSnapshot
|
||||||
|
apiGroup: snapshot.storage.k8s.io
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
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
|
||||||
|
|
||||||
|
display:
|
||||||
|
spice:
|
||||||
|
port: <%= $((5910 + number)) %>
|
||||||
|
|
@ -5,18 +5,13 @@ metadata:
|
||||||
name: test-vm
|
name: test-vm
|
||||||
spec:
|
spec:
|
||||||
image:
|
image:
|
||||||
repository: docker-registry.lan.mnl.de
|
source: registry.mnl.de/org/jdrupes/vm-operator/org.jdrupes.vmoperator.runner.qemu-arch:testing
|
||||||
path: vmoperator/org.jdrupes.vmoperator.runner.qemu-alpine
|
|
||||||
version: latest
|
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
- user: admin
|
- user: admin
|
||||||
may:
|
may:
|
||||||
- "*"
|
- "*"
|
||||||
- user: test
|
|
||||||
may:
|
|
||||||
- "accessConsole"
|
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
|
@ -37,8 +32,9 @@ spec:
|
||||||
currentCpus: 4
|
currentCpus: 4
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
- tap:
|
# No bridge on test cluster
|
||||||
mac: "02:16:3e:33:58:10"
|
- user: {}
|
||||||
|
|
||||||
disks:
|
disks:
|
||||||
- volumeClaimTemplate:
|
- volumeClaimTemplate:
|
||||||
metadata:
|
metadata:
|
||||||
|
|
@ -62,3 +58,5 @@ spec:
|
||||||
spice:
|
spice:
|
||||||
port: 5810
|
port: 5810
|
||||||
generateSecret: true
|
generateSecret: true
|
||||||
|
|
||||||
|
loadBalancerService: {}
|
||||||
|
|
|
||||||
2
dev-example/vmop-agent/99-vmop-agent.rules
Normal file
2
dev-example/vmop-agent/99-vmop-agent.rules
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
SUBSYSTEM=="virtio-ports", ATTR{name}=="org.jdrupes.vmop_agent.0", \
|
||||||
|
TAG+="systemd" ENV{SYSTEMD_WANTS}="vmop-agent.service"
|
||||||
3
dev-example/vmop-agent/gdm/PostLogin/Default
Executable file
3
dev-example/vmop-agent/gdm/PostLogin/Default
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
sed -i '/AutomaticLogin/d' /etc/gdm/custom.conf
|
||||||
146
dev-example/vmop-agent/vmop-agent
Executable file
146
dev-example/vmop-agent/vmop-agent
Executable file
|
|
@ -0,0 +1,146 @@
|
||||||
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
# Note that this script requires "jq" to be installed and a version
|
||||||
|
# of loginctl that accepts the "-j" option.
|
||||||
|
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--path) shift; ttyPath="$1";;
|
||||||
|
--path=*) IFS='=' read -r option value <<< "$1"; ttyPath="$value";;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
ttyPath="${ttyPath:-/dev/virtio-ports/org.jdrupes.vmop_agent.0}"
|
||||||
|
|
||||||
|
if [ ! -w "$ttyPath" ]; then
|
||||||
|
echo >&2 "Device $ttyPath not writable"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create fd for the tty in variable con
|
||||||
|
if ! exec {con}<>"$ttyPath"; then
|
||||||
|
echo >&2 "Cannot open device $ttyPath"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Temporary file for logging error messages, clear tty and signal ready
|
||||||
|
temperr=$(mktemp)
|
||||||
|
clear >/dev/tty1
|
||||||
|
echo >&${con} "220 Hello"
|
||||||
|
|
||||||
|
# This script uses the (shared) home directory as "dictonary" for
|
||||||
|
# synchronizing the username and the uid between hosts.
|
||||||
|
#
|
||||||
|
# Every user has a directory with his username. The directory is
|
||||||
|
# owned by root to prevent changes of access rights by the user.
|
||||||
|
# The uid and gid of the directory are equal. Thus the name of the
|
||||||
|
# directory and the id from the group ownership also provide the
|
||||||
|
# association between the username and the uid.
|
||||||
|
|
||||||
|
# Add the user with name $1 to the host's "user database". This
|
||||||
|
# may not be invoked concurrently.
|
||||||
|
createUser() {
|
||||||
|
local missing=$1
|
||||||
|
local uid
|
||||||
|
local userHome="/home/$missing"
|
||||||
|
local createOpts=""
|
||||||
|
|
||||||
|
# Retrieve or create the uid for the username
|
||||||
|
if [ -d "$userHome" ]; then
|
||||||
|
# If a home directory exists, use the id from the group ownership as uid
|
||||||
|
uid=$(ls -ldn "$userHome" | head -n 1 | awk '{print $4}')
|
||||||
|
createOpts="--no-create-home"
|
||||||
|
else
|
||||||
|
# Else get the maximum of all ids from the group ownership +1
|
||||||
|
uid=$(ls -ln "/home" | tail -n +2 | awk '{print $4}' | sort | tail -1)
|
||||||
|
uid=$(( $uid + 1 ))
|
||||||
|
if [ $uid -lt 1100 ]; then
|
||||||
|
uid=1100
|
||||||
|
fi
|
||||||
|
createOpts="--create-home"
|
||||||
|
fi
|
||||||
|
groupadd -g $uid $missing
|
||||||
|
useradd $missing -u $uid -g $uid $createOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
# Login the user, i.e. create a desktopn for the user.
|
||||||
|
doLogin() {
|
||||||
|
user=$1
|
||||||
|
if [ "$user" = "root" ]; then
|
||||||
|
echo >&${con} "504 Won't log in root"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if this user is already logged in on tty2
|
||||||
|
curUser=$(loginctl -j | jq -r '.[] | select(.tty=="tty2") | .user')
|
||||||
|
if [ "$curUser" = "$user" ]; then
|
||||||
|
echo >&${con} "201 User already logged in"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Terminate a running desktop (fail safe)
|
||||||
|
attemptLogout
|
||||||
|
|
||||||
|
# Check if username is known on this host. If not, create user
|
||||||
|
uid=$(id -u ${user} 2>/dev/null)
|
||||||
|
if [ $? != 0 ]; then
|
||||||
|
( flock 200
|
||||||
|
createUser ${user}
|
||||||
|
) 200>/home/.gen-uid-lock
|
||||||
|
|
||||||
|
# This should now work, else something went wrong
|
||||||
|
uid=$(id -u ${user} 2>/dev/null)
|
||||||
|
if [ $? != 0 ]; then
|
||||||
|
echo >&${con} "451 Cannot determine uid"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Configure user as auto login user
|
||||||
|
sed -i '/AutomaticLogin/d' /etc/gdm/custom.conf
|
||||||
|
sed -i '/\[daemon\]/a AutomaticLoginEnable=true\nAutomaticLogin='$user \
|
||||||
|
/etc/gdm/custom.conf
|
||||||
|
|
||||||
|
# Activate user
|
||||||
|
systemctl restart gdm
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo >&${con} "201 User logged in successfully"
|
||||||
|
else
|
||||||
|
echo >&${con} "451 $(tr '\n' ' ' <${temperr})"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Attempt to log out a user currently using tty1. This is an intermediate
|
||||||
|
# operation that can be invoked from other operations
|
||||||
|
attemptLogout() {
|
||||||
|
sed -i '/AutomaticLogin/d' /etc/gdm/custom.conf
|
||||||
|
systemctl stop gdm
|
||||||
|
echo >&${con} "102 Desktop stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Log out any user currently using tty1. This is invoked when executing
|
||||||
|
# the logout command and therefore sends back a 2xx return code.
|
||||||
|
# Also try to restart gdm, if it is not running.
|
||||||
|
doLogout() {
|
||||||
|
attemptLogout
|
||||||
|
systemctl restart gdm
|
||||||
|
echo >&${con} "202 User logged out"
|
||||||
|
}
|
||||||
|
|
||||||
|
while read line <&${con}; do
|
||||||
|
case $line in
|
||||||
|
"login "*) IFS=' ' read -ra args <<< "$line"; doLogin ${args[1]};;
|
||||||
|
"logout") doLogout;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
onExit() {
|
||||||
|
doLogout
|
||||||
|
if [ -n "$temperr" ]; then
|
||||||
|
rm -f $temperr
|
||||||
|
fi
|
||||||
|
echo >&${con} "240 Quit"
|
||||||
|
}
|
||||||
|
|
||||||
|
trap onExit EXIT
|
||||||
15
dev-example/vmop-agent/vmop-agent.service
Normal file
15
dev-example/vmop-agent/vmop-agent.service
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[Unit]
|
||||||
|
Description=VM-Operator (Guest) Agent
|
||||||
|
BindsTo=dev-virtio\x2dports-org.jdrupes.vmop_agent.0.device
|
||||||
|
After=dev-virtio\x2dports-org.jdrupes.vmop_agent.0.device multi-user.target
|
||||||
|
IgnoreOnIsolate=True
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
UMask=0077
|
||||||
|
#EnvironmentFile=/etc/sysconfig/vmop-agent
|
||||||
|
ExecStart=/usr/local/libexec/vmop-agent
|
||||||
|
Restart=always
|
||||||
|
RestartSec=0
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=dev-virtio\x2dports-org.jdrupes.vmop_agent.0.device
|
||||||
1
gradle.properties
Normal file
1
gradle.properties
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
org.gradle.parallel=true
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,7 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
||||||
31
gradlew
vendored
31
gradlew
vendored
|
|
@ -55,7 +55,7 @@
|
||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
|
@ -83,10 +83,8 @@ done
|
||||||
# This is normally unused
|
# This is normally unused
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
|
@ -133,10 +131,13 @@ location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
|
|
@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
# shellcheck disable=SC3045
|
# shellcheck disable=SC2039,SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
|
|
@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
# shellcheck disable=SC3045
|
# shellcheck disable=SC2039,SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
|
|
@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
# double quotes to make sure that they get re-expanded; and
|
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
|
|
||||||
20
gradlew.bat
vendored
20
gradlew.bat
vendored
|
|
@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|
@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2
misc/javadoc-overwrites.css
Normal file
2
misc/javadoc-overwrites.css
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
:root { --body-font-size: 16px;}
|
||||||
|
:root { --code-font-size: 16px;}
|
||||||
|
|
@ -4,26 +4,33 @@
|
||||||
<a href="https://github.com/site/terms" target="_top">Terms</a>
|
<a href="https://github.com/site/terms" target="_top">Terms</a>
|
||||||
— <a href="https://github.com/site/privacy" target="_top">Privacy</a></p>
|
— <a href="https://github.com/site/privacy" target="_top">Privacy</a></p>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
if (location.hostname.indexOf("github") !== -1) {
|
if (location.hostname.indexOf("github") !== -1 || location.hostname.indexOf("jdrupes.org") !== -1) {
|
||||||
document.getElementById("githubfooter").style.visibility="visible";
|
document.getElementById("githubfooter").style.visibility="visible";
|
||||||
var _paq = _paq || [];
|
|
||||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
|
||||||
_paq.push(["setDocumentTitle", document.domain + "/" + document.title]);
|
|
||||||
_paq.push(["setCookieDomain", "*.mnlipp.github.io"]);
|
|
||||||
_paq.push(["setDomains", ["*.mnlipp.github.io"]]);
|
|
||||||
_paq.push(['disableCookies']);
|
|
||||||
_paq.push(['trackPageView']);
|
|
||||||
_paq.push(['enableLinkTracking']);
|
|
||||||
(function() {
|
|
||||||
var u="//piwik.mnl.de/";
|
|
||||||
_paq.push(['setTrackerUrl', u+'piwik.php']);
|
|
||||||
_paq.push(['setSiteId', '14']);
|
|
||||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
|
||||||
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<noscript>
|
<noscript>
|
||||||
<div>JavaScript is disabled on your browser, terms and privacy links may not be shown correctly.</div>
|
<div>JavaScript is disabled on your browser, terms and privacy links may not be shown correctly.</div>
|
||||||
</noscript>
|
</noscript>
|
||||||
|
<!-- Matomo anonymous, no cookies (https://matomo.org/blog/2018/04/how-to-not-process-any-personal-data-with-matomo-and-what-it-means-for-you/) -->
|
||||||
|
<script>
|
||||||
|
var _paq = _paq || [];
|
||||||
|
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||||
|
_paq.push(["setDocumentTitle", document.domain + "/" + document.title]);
|
||||||
|
_paq.push(["setCookieDomain", "*.mnlipp.github.io"]);
|
||||||
|
_paq.push(["setDomains", ["*.mnlipp.github.io", "*.jdrupes.org", "kubernetes-vm-operator.readthedocs.io"]]);
|
||||||
|
_paq.push(['disableCookies']);
|
||||||
|
_paq.push(['trackPageView']);
|
||||||
|
_paq.push(['enableLinkTracking']);
|
||||||
|
(function() {
|
||||||
|
var u="https://piwik.mnl.de/";
|
||||||
|
_paq.push(['setTrackerUrl', u+'piwik.php']);
|
||||||
|
_paq.push(['setSiteId', '17']);
|
||||||
|
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=17&rec=1&action_name=VM-Operator" style="border:0;" alt="" /></p></noscript>
|
||||||
|
<!-- End Matomo Code -->
|
||||||
|
<script defer src="https://gotit.mnl.de/script.js" data-website-id="14b277ad-d330-4a54-82f1-a77d111240ac"></script>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1,912 +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 Serif", 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-family: "DejaVu Sans", sans;
|
|
||||||
font-size:20px;
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
font-size:18px;
|
|
||||||
}
|
|
||||||
h3 {
|
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
font-size:16px;
|
|
||||||
}
|
|
||||||
h4 {
|
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
font-size:15px;
|
|
||||||
}
|
|
||||||
h5 {
|
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
font-size:14px;
|
|
||||||
}
|
|
||||||
h6 {
|
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
font-size:13px;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
list-style-type:disc;
|
|
||||||
}
|
|
||||||
code, tt {
|
|
||||||
font-family: "DejaVu Sans Mono", monospace;
|
|
||||||
}
|
|
||||||
: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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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-family: "DejaVu Sans", sans;
|
|
||||||
font-size:80%;
|
|
||||||
}
|
|
||||||
.sub-nav {
|
|
||||||
background-color:#dee3e9;
|
|
||||||
float:left;
|
|
||||||
width:100%;
|
|
||||||
overflow:hidden;
|
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
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", sans;
|
|
||||||
font-weight:bold;
|
|
||||||
margin:10px 0 0 0;
|
|
||||||
color:#4E4E4E;
|
|
||||||
}
|
|
||||||
dl.notes > dd {
|
|
||||||
margin:5px 10px 10px 0;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
.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;
|
|
||||||
font-family: 'DejaVu Sans';
|
|
||||||
}
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
#method-summary-table .three-column-summary {
|
|
||||||
grid-template-columns: minmax(10%, 20%) minmax(15%, max-content) minmax(15%, auto);
|
|
||||||
}
|
|
||||||
.four-column-summary {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(10%, max-content) minmax(10%, max-content) minmax(10%, max-content) minmax(10%, auto);
|
|
||||||
}
|
|
||||||
@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-family: 'DejaVu Sans';
|
|
||||||
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-size:14px;
|
|
||||||
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;
|
|
||||||
/* font-size:14px; */
|
|
||||||
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-family: "DejaVu Sans", sans;
|
|
||||||
font-weight:bold;
|
|
||||||
}
|
|
||||||
.sub-title, .inheritance, .all-packages-table-tab1.col-first,
|
|
||||||
.summary-table .col-first {
|
|
||||||
font-family: "DejaVu Sans", sans;
|
|
||||||
}
|
|
||||||
.deprecation-comment, .help-footnote, .preview-comment {
|
|
||||||
font-style:italic;
|
|
||||||
}
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
ul.ui-autocomplete li {
|
|
||||||
float:left;
|
|
||||||
clear:both;
|
|
||||||
width:100%;
|
|
||||||
}
|
|
||||||
.result-highlight {
|
|
||||||
font-weight:bold;
|
|
||||||
}
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,6 +10,8 @@ plugins {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(':org.jdrupes.vmoperator.util')
|
api project(':org.jdrupes.vmoperator.util')
|
||||||
|
api 'org.jgrapes:org.jgrapes.core:[1.22.1,2)'
|
||||||
api 'io.kubernetes:client-java:[19.0.0,20.0.0)'
|
api 'io.kubernetes:client-java:[19.0.0,20.0.0)'
|
||||||
api 'org.yaml:snakeyaml'
|
api 'org.yaml:snakeyaml'
|
||||||
|
api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:[2.16.1,3]'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,15 +27,101 @@ public class Constants {
|
||||||
/** The Constant APP_NAME. */
|
/** The Constant APP_NAME. */
|
||||||
public static final String APP_NAME = "vm-runner";
|
public static final String APP_NAME = "vm-runner";
|
||||||
|
|
||||||
/** The Constant COMP_DISPLAY_SECRETS. */
|
|
||||||
public static final String COMP_DISPLAY_SECRET = "display-secret";
|
|
||||||
|
|
||||||
/** The Constant VM_OP_NAME. */
|
/** The Constant VM_OP_NAME. */
|
||||||
public static final String VM_OP_NAME = "vm-operator";
|
public static final String VM_OP_NAME = "vm-operator";
|
||||||
|
|
||||||
/** The Constant VM_OP_GROUP. */
|
/**
|
||||||
public static final String VM_OP_GROUP = "vmoperator.jdrupes.org";
|
* Constants related to the CRD.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.ShortClassName")
|
||||||
|
public static class Crd {
|
||||||
|
/** The Constant GROUP. */
|
||||||
|
public static final String GROUP = "vmoperator.jdrupes.org";
|
||||||
|
|
||||||
/** The Constant VM_OP_KIND_VM. */
|
/** The Constant KIND_VM. */
|
||||||
public static final String VM_OP_KIND_VM = "VirtualMachine";
|
public static final String KIND_VM = "VirtualMachine";
|
||||||
|
|
||||||
|
/** The Constant KIND_VM_POOL. */
|
||||||
|
public static final String KIND_VM_POOL = "VmPool";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status related constants.
|
||||||
|
*/
|
||||||
|
public static class Status {
|
||||||
|
/** The Constant RUNNER_VERSION. */
|
||||||
|
public static final String RUNNER_VERSION = "runnerVersion";
|
||||||
|
|
||||||
|
/** The Constant CPUS. */
|
||||||
|
public static final String CPUS = "cpus";
|
||||||
|
|
||||||
|
/** The Constant RAM. */
|
||||||
|
public static final String RAM = "ram";
|
||||||
|
|
||||||
|
/** The Constant OSINFO. */
|
||||||
|
public static final String OSINFO = "osinfo";
|
||||||
|
|
||||||
|
/** The Constant DISPLAY_PASSWORD_SERIAL. */
|
||||||
|
public static final String DISPLAY_PASSWORD_SERIAL
|
||||||
|
= "displayPasswordSerial";
|
||||||
|
|
||||||
|
/** The Constant LOGGED_IN_USER. */
|
||||||
|
public static final String LOGGED_IN_USER = "loggedInUser";
|
||||||
|
|
||||||
|
/** The Constant CONSOLE_CLIENT. */
|
||||||
|
public static final String CONSOLE_CLIENT = "consoleClient";
|
||||||
|
|
||||||
|
/** The Constant CONSOLE_USER. */
|
||||||
|
public static final String CONSOLE_USER = "consoleUser";
|
||||||
|
|
||||||
|
/** The Constant ASSIGNMENT. */
|
||||||
|
public static final String ASSIGNMENT = "assignment";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conditions used in Status.
|
||||||
|
*/
|
||||||
|
public static class Condition {
|
||||||
|
/** The Constant COND_RUNNING. */
|
||||||
|
public static final String RUNNING = "Running";
|
||||||
|
|
||||||
|
/** The Constant COND_BOOTED. */
|
||||||
|
public static final String BOOTED = "Booted";
|
||||||
|
|
||||||
|
/** The Constant COND_VMOP_AGENT. */
|
||||||
|
public static final String VMOP_AGENT = "VmopAgentConnected";
|
||||||
|
|
||||||
|
/** The Constant COND_USER_LOGGED_IN. */
|
||||||
|
public static final String USER_LOGGED_IN = "UserLoggedIn";
|
||||||
|
|
||||||
|
/** The Constant COND_CONSOLE. */
|
||||||
|
public static final String CONSOLE_CONNECTED = "ConsoleConnected";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reasons used in conditions.
|
||||||
|
*/
|
||||||
|
public static class Reason {
|
||||||
|
/** The Constant NOT_REQUESTED. */
|
||||||
|
public static final String NOT_REQUESTED = "NotRequested";
|
||||||
|
|
||||||
|
/** The Constant USER_LOGGED_IN. */
|
||||||
|
public static final String LOGGED_IN = "LoggedIn";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DisplaySecret related constants.
|
||||||
|
*/
|
||||||
|
public static class DisplaySecret {
|
||||||
|
|
||||||
|
/** The Constant NAME. */
|
||||||
|
public static final String NAME = "display-secret";
|
||||||
|
|
||||||
|
/** The Constant PASSWORD. */
|
||||||
|
public static final String PASSWORD = "display-password";
|
||||||
|
|
||||||
|
/** The Constant EXPIRY. */
|
||||||
|
public static final String EXPIRY = "password-expiry";
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,11 @@ import java.util.regex.Pattern;
|
||||||
public class Convertions {
|
public class Convertions {
|
||||||
|
|
||||||
@SuppressWarnings({ "PMD.UseConcurrentHashMap",
|
@SuppressWarnings({ "PMD.UseConcurrentHashMap",
|
||||||
"PMD.FieldNamingConventions", "PMD.VariableNamingConventions" })
|
"PMD.FieldNamingConventions" })
|
||||||
private static final Map<String, BigInteger> unitMap = new HashMap<>();
|
private static final Map<String, BigInteger> unitMap = new HashMap<>();
|
||||||
@SuppressWarnings({ "PMD.FieldNamingConventions",
|
@SuppressWarnings({ "PMD.FieldNamingConventions" })
|
||||||
"PMD.VariableNamingConventions" })
|
|
||||||
private static final List<Map.Entry<String, BigInteger>> unitMappings;
|
private static final List<Map.Entry<String, BigInteger>> unitMappings;
|
||||||
@SuppressWarnings({ "PMD.FieldNamingConventions",
|
@SuppressWarnings({ "PMD.FieldNamingConventions" })
|
||||||
"PMD.VariableNamingConventions" })
|
|
||||||
private static final Pattern memorySize
|
private static final Pattern memorySize
|
||||||
= Pattern.compile("^\\s*(\\d+(\\.\\d+)?)\\s*([A-Za-z]*)\\s*");
|
= Pattern.compile("^\\s*(\\d+(\\.\\d+)?)\\s*([A-Za-z]*)\\s*");
|
||||||
|
|
||||||
|
|
@ -69,7 +67,6 @@ public class Convertions {
|
||||||
* @param amount the amount
|
* @param amount the amount
|
||||||
* @return the big integer
|
* @return the big integer
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
|
||||||
public static BigInteger parseMemory(Object amount) {
|
public static BigInteger parseMemory(Object amount) {
|
||||||
if (amount == null) {
|
if (amount == null) {
|
||||||
return (BigInteger) amount;
|
return (BigInteger) amount;
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||||
/**
|
/**
|
||||||
* Helpers for K8s API.
|
* Helpers for K8s API.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.ShortClassName", "PMD.UseUtilityClass",
|
@SuppressWarnings({ "PMD.ShortClassName", "PMD.UseUtilityClass" })
|
||||||
"PMD.DataflowAnomalyAnalysis" })
|
|
||||||
public class K8s {
|
public class K8s {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -113,7 +112,6 @@ public class K8s {
|
||||||
public static JsonObject yamlToJson(ApiClient client, Reader yaml) {
|
public static JsonObject yamlToJson(ApiClient client, Reader yaml) {
|
||||||
// Avoid Yaml.load due to
|
// Avoid Yaml.load due to
|
||||||
// https://github.com/kubernetes-client/java/issues/2741
|
// https://github.com/kubernetes-client/java/issues/2741
|
||||||
@SuppressWarnings("PMD.UseConcurrentHashMap")
|
|
||||||
Map<String, Object> yamlData
|
Map<String, Object> yamlData
|
||||||
= new Yaml(new SafeConstructor(new LoaderOptions())).load(yaml);
|
= new Yaml(new SafeConstructor(new LoaderOptions())).load(yaml);
|
||||||
|
|
||||||
|
|
@ -157,27 +155,6 @@ public class K8s {
|
||||||
return Optional.of(apiRes);
|
return Optional.of(apiRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an object from its metadata.
|
|
||||||
*
|
|
||||||
* @param <T> the generic type
|
|
||||||
* @param <LT> the generic type
|
|
||||||
* @param api the api
|
|
||||||
* @param meta the meta
|
|
||||||
* @return the object
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
@SuppressWarnings("PMD.GenericsNaming")
|
|
||||||
public static <T extends KubernetesObject, LT extends KubernetesListObject>
|
|
||||||
Optional<T>
|
|
||||||
get(GenericKubernetesApi<T, LT> api, V1ObjectMeta meta) {
|
|
||||||
var response = api.get(meta.getNamespace(), meta.getName());
|
|
||||||
if (response.isSuccess()) {
|
|
||||||
return Optional.of(response.getObject());
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply the given patch data.
|
* Apply the given patch data.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,7 @@ import okhttp3.Response;
|
||||||
* A client with some additional properties.
|
* A client with some additional properties.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.ExcessivePublicCount", "PMD.TooManyMethods",
|
@SuppressWarnings({ "PMD.ExcessivePublicCount", "PMD.TooManyMethods",
|
||||||
"PMD.LinguisticNaming", "checkstyle:LineLength",
|
"checkstyle:LineLength", "PMD.CouplingBetweenObjects", "PMD.GodClass" })
|
||||||
"PMD.CouplingBetweenObjects", "PMD.GodClass" })
|
|
||||||
public class K8sClient extends ApiClient {
|
public class K8sClient extends ApiClient {
|
||||||
|
|
||||||
private ApiClient apiClient;
|
private ApiClient apiClient;
|
||||||
|
|
@ -231,7 +230,6 @@ public class K8sClient extends ApiClient {
|
||||||
* @return the api client
|
* @return the api client
|
||||||
* @see ApiClient#setKeyManagers(javax.net.ssl.KeyManager[])
|
* @see ApiClient#setKeyManagers(javax.net.ssl.KeyManager[])
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.UseVarargs")
|
|
||||||
@Override
|
@Override
|
||||||
public ApiClient setKeyManagers(KeyManager[] managers) {
|
public ApiClient setKeyManagers(KeyManager[] managers) {
|
||||||
return apiClient().setKeyManagers(managers);
|
return apiClient().setKeyManagers(managers);
|
||||||
|
|
@ -638,7 +636,6 @@ public class K8sClient extends ApiClient {
|
||||||
* @return the string
|
* @return the string
|
||||||
* @see ApiClient#selectHeaderAccept(java.lang.String[])
|
* @see ApiClient#selectHeaderAccept(java.lang.String[])
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.UseVarargs")
|
|
||||||
@Override
|
@Override
|
||||||
public String selectHeaderAccept(String[] accepts) {
|
public String selectHeaderAccept(String[] accepts) {
|
||||||
return apiClient().selectHeaderAccept(accepts);
|
return apiClient().selectHeaderAccept(accepts);
|
||||||
|
|
@ -651,7 +648,6 @@ public class K8sClient extends ApiClient {
|
||||||
* @return the string
|
* @return the string
|
||||||
* @see ApiClient#selectHeaderContentType(java.lang.String[])
|
* @see ApiClient#selectHeaderContentType(java.lang.String[])
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.UseVarargs")
|
|
||||||
@Override
|
@Override
|
||||||
public String selectHeaderContentType(String[] contentTypes) {
|
public String selectHeaderContentType(String[] contentTypes) {
|
||||||
return apiClient().selectHeaderContentType(contentTypes);
|
return apiClient().selectHeaderContentType(contentTypes);
|
||||||
|
|
@ -818,7 +814,7 @@ public class K8sClient extends ApiClient {
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
* @see ApiClient#buildCall(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.Object, java.util.Map, java.util.Map, java.util.Map, java.lang.String[], io.kubernetes.client.openapi.ApiCallback)
|
* @see ApiClient#buildCall(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.Object, java.util.Map, java.util.Map, java.util.Map, java.lang.String[], io.kubernetes.client.openapi.ApiCallback)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "rawtypes", "PMD.ExcessiveParameterList" })
|
@SuppressWarnings({ "rawtypes" })
|
||||||
@Override
|
@Override
|
||||||
public Call buildCall(String path, String method, List<Pair> queryParams,
|
public Call buildCall(String path, String method, List<Pair> queryParams,
|
||||||
List<Pair> collectionQueryParams, Object body,
|
List<Pair> collectionQueryParams, Object body,
|
||||||
|
|
@ -847,7 +843,7 @@ public class K8sClient extends ApiClient {
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
* @see ApiClient#buildRequest(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.Object, java.util.Map, java.util.Map, java.util.Map, java.lang.String[], io.kubernetes.client.openapi.ApiCallback)
|
* @see ApiClient#buildRequest(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.Object, java.util.Map, java.util.Map, java.util.Map, java.lang.String[], io.kubernetes.client.openapi.ApiCallback)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "rawtypes", "PMD.ExcessiveParameterList" })
|
@SuppressWarnings({ "rawtypes" })
|
||||||
@Override
|
@Override
|
||||||
public Request buildRequest(String path, String method,
|
public Request buildRequest(String path, String method,
|
||||||
List<Pair> queryParams, List<Pair> collectionQueryParams,
|
List<Pair> queryParams, List<Pair> collectionQueryParams,
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ import java.util.function.Function;
|
||||||
* @param <O> the generic type
|
* @param <O> the generic type
|
||||||
* @param <L> the generic type
|
* @param <L> the generic type
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
@SuppressWarnings({ "PMD.CouplingBetweenObjects" })
|
||||||
public class K8sClusterGenericStub<O extends KubernetesObject,
|
public class K8sClusterGenericStub<O extends KubernetesObject,
|
||||||
L extends KubernetesListObject> {
|
L extends KubernetesListObject> {
|
||||||
protected final K8sClient client;
|
protected final K8sClient client;
|
||||||
|
|
@ -239,6 +239,7 @@ public class K8sClusterGenericStub<O extends KubernetesObject,
|
||||||
* @param <L> the object list type
|
* @param <L> the object list type
|
||||||
* @param <R> the result type
|
* @param <R> the result type
|
||||||
*/
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
public interface GenericSupplier<O extends KubernetesObject,
|
public interface GenericSupplier<O extends KubernetesObject,
|
||||||
L extends KubernetesListObject,
|
L extends KubernetesListObject,
|
||||||
R extends K8sClusterGenericStub<O, L>> {
|
R extends K8sClusterGenericStub<O, L>> {
|
||||||
|
|
@ -253,7 +254,6 @@ public class K8sClusterGenericStub<O extends KubernetesObject,
|
||||||
* @param name the name
|
* @param name the name
|
||||||
* @return the result
|
* @return the result
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.UseObjectForClearerAPI")
|
|
||||||
R get(Class<O> objectClass, Class<L> objectListClass, K8sClient client,
|
R get(Class<O> objectClass, Class<L> objectListClass, K8sClient client,
|
||||||
APIResource context, String name);
|
APIResource context, String name);
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +282,6 @@ public class K8sClusterGenericStub<O extends KubernetesObject,
|
||||||
* @return the stub if the object exists
|
* @return the stub if the object exists
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop" })
|
|
||||||
public static <O extends KubernetesObject, L extends KubernetesListObject,
|
public static <O extends KubernetesObject, L extends KubernetesListObject,
|
||||||
R extends K8sClusterGenericStub<O, L>>
|
R extends K8sClusterGenericStub<O, L>>
|
||||||
R get(Class<O> objectClass, Class<L> objectListClass,
|
R get(Class<O> objectClass, Class<L> objectListClass,
|
||||||
|
|
@ -313,8 +312,6 @@ public class K8sClusterGenericStub<O extends KubernetesObject,
|
||||||
* @return the stub if the object exists
|
* @return the stub if the object exists
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
|
|
||||||
"PMD.UseObjectForClearerAPI" })
|
|
||||||
public static <O extends KubernetesObject, L extends KubernetesListObject,
|
public static <O extends KubernetesObject, L extends KubernetesListObject,
|
||||||
R extends K8sClusterGenericStub<O, L>>
|
R extends K8sClusterGenericStub<O, L>>
|
||||||
R get(Class<O> objectClass, Class<L> objectListClass,
|
R get(Class<O> objectClass, Class<L> objectListClass,
|
||||||
|
|
@ -339,8 +336,6 @@ public class K8sClusterGenericStub<O extends KubernetesObject,
|
||||||
* @return the stub if the object exists
|
* @return the stub if the object exists
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
|
|
||||||
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
|
|
||||||
public static <O extends KubernetesObject, L extends KubernetesListObject,
|
public static <O extends KubernetesObject, L extends KubernetesListObject,
|
||||||
R extends K8sClusterGenericStub<O, L>>
|
R extends K8sClusterGenericStub<O, L>>
|
||||||
R create(Class<O> objectClass, Class<L> objectListClass,
|
R create(Class<O> objectClass, Class<L> objectListClass,
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||||
* notably the metadata, is made available through the methods
|
* notably the metadata, is made available through the methods
|
||||||
* defined by {@link KubernetesObject}.
|
* defined by {@link KubernetesObject}.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataClass")
|
|
||||||
public class K8sDynamicModel implements KubernetesObject {
|
public class K8sDynamicModel implements KubernetesObject {
|
||||||
|
|
||||||
private final V1ObjectMeta metadata;
|
private final V1ObjectMeta metadata;
|
||||||
|
|
@ -102,7 +101,7 @@ public class K8sDynamicModel implements KubernetesObject {
|
||||||
*
|
*
|
||||||
* @return the JSON object describing the status
|
* @return the JSON object describing the status
|
||||||
*/
|
*/
|
||||||
public JsonObject status() {
|
public JsonObject statusJson() {
|
||||||
return data.getAsJsonObject("status");
|
return data.getAsJsonObject("status");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import java.util.Collection;
|
||||||
* state and can therefore be used for any kind of object, especially
|
* state and can therefore be used for any kind of object, especially
|
||||||
* custom objects.
|
* custom objects.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
|
||||||
public class K8sDynamicStub
|
public class K8sDynamicStub
|
||||||
extends K8sDynamicStubBase<K8sDynamicModel, K8sDynamicModels> {
|
extends K8sDynamicStubBase<K8sDynamicModel, K8sDynamicModels> {
|
||||||
|
|
||||||
|
|
@ -64,8 +63,6 @@ public class K8sDynamicStub
|
||||||
* @return the stub if the object exists
|
* @return the stub if the object exists
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
|
|
||||||
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
|
|
||||||
public static K8sDynamicStub get(K8sClient client,
|
public static K8sDynamicStub get(K8sClient client,
|
||||||
GroupVersionKind gvk, String namespace, String name)
|
GroupVersionKind gvk, String namespace, String name)
|
||||||
throws ApiException {
|
throws ApiException {
|
||||||
|
|
@ -83,8 +80,6 @@ public class K8sDynamicStub
|
||||||
* @return the stub if the object exists
|
* @return the stub if the object exists
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
|
|
||||||
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
|
|
||||||
public static K8sDynamicStub get(K8sClient client,
|
public static K8sDynamicStub get(K8sClient client,
|
||||||
APIResource context, String namespace, String name) {
|
APIResource context, String namespace, String name) {
|
||||||
return new K8sDynamicStub(client, context, namespace, name);
|
return new K8sDynamicStub(client, context, namespace, name);
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import io.kubernetes.client.Discovery.APIResource;
|
||||||
* state and can therefore be used for any kind of object, especially
|
* state and can therefore be used for any kind of object, especially
|
||||||
* custom objects.
|
* custom objects.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
|
||||||
public abstract class K8sDynamicStubBase<O extends K8sDynamicModel,
|
public abstract class K8sDynamicStubBase<O extends K8sDynamicModel,
|
||||||
L extends K8sDynamicModelsBase<O>> extends K8sGenericStub<O, L> {
|
L extends K8sDynamicModelsBase<O>> extends K8sGenericStub<O, L> {
|
||||||
|
|
||||||
|
|
@ -40,7 +39,6 @@ public abstract class K8sDynamicStubBase<O extends K8sDynamicModel,
|
||||||
* @param namespace the namespace
|
* @param namespace the namespace
|
||||||
* @param name the name
|
* @param name the name
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.ConstructorCallsOverridableMethod")
|
|
||||||
public K8sDynamicStubBase(Class<O> objectClass,
|
public K8sDynamicStubBase(Class<O> objectClass,
|
||||||
Class<L> objectListClass, DynamicTypeAdapterFactory<O, L> taf,
|
Class<L> objectListClass, DynamicTypeAdapterFactory<O, L> taf,
|
||||||
K8sClient client, APIResource context, String namespace,
|
K8sClient client, APIResource context, String namespace,
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import io.kubernetes.client.openapi.ApiException;
|
||||||
import io.kubernetes.client.util.Strings;
|
import io.kubernetes.client.util.Strings;
|
||||||
import io.kubernetes.client.util.generic.GenericKubernetesApi;
|
import io.kubernetes.client.util.generic.GenericKubernetesApi;
|
||||||
import io.kubernetes.client.util.generic.KubernetesApiResponse;
|
import io.kubernetes.client.util.generic.KubernetesApiResponse;
|
||||||
|
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
|
||||||
import io.kubernetes.client.util.generic.options.GetOptions;
|
import io.kubernetes.client.util.generic.options.GetOptions;
|
||||||
import io.kubernetes.client.util.generic.options.ListOptions;
|
import io.kubernetes.client.util.generic.options.ListOptions;
|
||||||
import io.kubernetes.client.util.generic.options.PatchOptions;
|
import io.kubernetes.client.util.generic.options.PatchOptions;
|
||||||
|
|
@ -47,7 +48,7 @@ import java.util.function.Function;
|
||||||
* @param <O> the generic type
|
* @param <O> the generic type
|
||||||
* @param <L> the generic type
|
* @param <L> the generic type
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
@SuppressWarnings({ "PMD.TooManyMethods" })
|
||||||
public class K8sGenericStub<O extends KubernetesObject,
|
public class K8sGenericStub<O extends KubernetesObject,
|
||||||
L extends KubernetesListObject> {
|
L extends KubernetesListObject> {
|
||||||
protected final K8sClient client;
|
protected final K8sClient client;
|
||||||
|
|
@ -192,30 +193,92 @@ public class K8sGenericStub<O extends KubernetesObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the object's status.
|
* Updates the object's status. Does not retry in case of conflict.
|
||||||
*
|
*
|
||||||
* @param object the current state of the object (passed to `status`)
|
* @param object the current state of the object (passed to `status`)
|
||||||
* @param status function that returns the new status
|
* @param updater function that returns the new status
|
||||||
* @return the updated model or empty if not successful
|
* @return the updated model or empty if the object was not found
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
public Optional<O> updateStatus(O object,
|
public Optional<O> updateStatus(O object, Function<O, Object> updater)
|
||||||
Function<O, Object> status) throws ApiException {
|
throws ApiException {
|
||||||
return K8s.optional(api.updateStatus(object, status));
|
return K8s.optional(api.updateStatus(object, updater));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the status.
|
* Updates the status of the given object. In case of conflict,
|
||||||
|
* get the current version of the object and tries again. Retries
|
||||||
|
* up to `retries` times.
|
||||||
*
|
*
|
||||||
* @param status the status
|
* @param updater the function updating the status
|
||||||
|
* @param current the current state of the object, used for the first
|
||||||
|
* attempt to update
|
||||||
|
* @param retries the retries in case of conflict
|
||||||
|
* @return the updated model or empty if the object was not found
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "PMD.AssignmentInOperand" })
|
||||||
|
public Optional<O> updateStatus(Function<O, Object> updater, O current,
|
||||||
|
int retries) throws ApiException {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
if (current == null) {
|
||||||
|
current = api.get(namespace, name)
|
||||||
|
.throwsApiException().getObject();
|
||||||
|
}
|
||||||
|
return updateStatus(current, updater);
|
||||||
|
} catch (ApiException e) {
|
||||||
|
if (HttpURLConnection.HTTP_CONFLICT != e.getCode()
|
||||||
|
|| retries-- <= 0) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
// Get current version for new attempt
|
||||||
|
current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the object and updates the status. In case of conflict, retries
|
||||||
|
* up to `retries` times.
|
||||||
|
*
|
||||||
|
* @param updater the function updating the status
|
||||||
|
* @param retries the retries in case of conflict
|
||||||
|
* @return the updated model or empty if the object was not found
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
public Optional<O> updateStatus(Function<O, Object> updater, int retries)
|
||||||
|
throws ApiException {
|
||||||
|
return updateStatus(updater, null, retries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the status of the given object. In case of conflict,
|
||||||
|
* get the current version of the object and tries again. Retries
|
||||||
|
* up to `retries` times.
|
||||||
|
*
|
||||||
|
* @param updater the function updating the status
|
||||||
|
* @param current the current
|
||||||
* @return the kubernetes api response
|
* @return the kubernetes api response
|
||||||
* the updated model or empty if not successful
|
* the updated model or empty if not successful
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
public Optional<O> updateStatus(Function<O, Object> status)
|
public Optional<O> updateStatus(Function<O, Object> updater, O current)
|
||||||
throws ApiException {
|
throws ApiException {
|
||||||
return updateStatus(
|
return updateStatus(updater, current, 16);
|
||||||
api.get(namespace, name).throwsApiException().getObject(), status);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the status. In case of conflict, retries up to 16 times.
|
||||||
|
*
|
||||||
|
* @param updater the function updating the status
|
||||||
|
* @return the kubernetes api response
|
||||||
|
* the updated model or empty if not successful
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
public Optional<O> updateStatus(Function<O, Object> updater)
|
||||||
|
throws ApiException {
|
||||||
|
return updateStatus(updater, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -224,7 +287,7 @@ public class K8sGenericStub<O extends KubernetesObject,
|
||||||
* @param patchType the patch type
|
* @param patchType the patch type
|
||||||
* @param patch the patch
|
* @param patch the patch
|
||||||
* @param options the options
|
* @param options the options
|
||||||
* @return the kubernetes api response
|
* @return the kubernetes api response if successful
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
public Optional<O> patch(String patchType, V1Patch patch,
|
public Optional<O> patch(String patchType, V1Patch patch,
|
||||||
|
|
@ -239,7 +302,7 @@ public class K8sGenericStub<O extends KubernetesObject,
|
||||||
*
|
*
|
||||||
* @param patchType the patch type
|
* @param patchType the patch type
|
||||||
* @param patch the patch
|
* @param patch the patch
|
||||||
* @return the kubernetes api response
|
* @return the kubernetes api response if successful
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
public Optional<O>
|
public Optional<O>
|
||||||
|
|
@ -248,6 +311,21 @@ public class K8sGenericStub<O extends KubernetesObject,
|
||||||
return patch(patchType, patch, opts);
|
return patch(patchType, patch, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply the given definition.
|
||||||
|
*
|
||||||
|
* @param def the def
|
||||||
|
* @return the kubernetes api response if successful
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
public Optional<O> apply(DynamicKubernetesObject def) throws ApiException {
|
||||||
|
PatchOptions opts = new PatchOptions();
|
||||||
|
opts.setForce(true);
|
||||||
|
opts.setFieldManager("kubernetes-java-kubectl-apply");
|
||||||
|
return patch(V1Patch.PATCH_FORMAT_APPLY_YAML,
|
||||||
|
new V1Patch(client.getJSON().serialize(def)), opts);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the object.
|
* Update the object.
|
||||||
*
|
*
|
||||||
|
|
@ -279,6 +357,7 @@ public class K8sGenericStub<O extends KubernetesObject,
|
||||||
* @param <L> the object list type
|
* @param <L> the object list type
|
||||||
* @param <R> the result type
|
* @param <R> the result type
|
||||||
*/
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
public interface GenericSupplier<O extends KubernetesObject,
|
public interface GenericSupplier<O extends KubernetesObject,
|
||||||
L extends KubernetesListObject, R extends K8sGenericStub<O, L>> {
|
L extends KubernetesListObject, R extends K8sGenericStub<O, L>> {
|
||||||
|
|
||||||
|
|
@ -290,7 +369,6 @@ public class K8sGenericStub<O extends KubernetesObject,
|
||||||
* @param name the name
|
* @param name the name
|
||||||
* @return the result
|
* @return the result
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.UseObjectForClearerAPI")
|
|
||||||
R get(K8sClient client, String namespace, String name);
|
R get(K8sClient client, String namespace, String name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -316,8 +394,6 @@ public class K8sGenericStub<O extends KubernetesObject,
|
||||||
* @return the stub if the object exists
|
* @return the stub if the object exists
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
|
|
||||||
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
|
|
||||||
public static <O extends KubernetesObject, L extends KubernetesListObject,
|
public static <O extends KubernetesObject, L extends KubernetesListObject,
|
||||||
R extends K8sGenericStub<O, L>>
|
R extends K8sGenericStub<O, L>>
|
||||||
R create(Class<O> objectClass, Class<L> objectListClass,
|
R create(Class<O> objectClass, Class<L> objectListClass,
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,11 @@ import io.kubernetes.client.util.generic.GenericKubernetesApi;
|
||||||
import io.kubernetes.client.util.generic.options.ListOptions;
|
import io.kubernetes.client.util.generic.options.ListOptions;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import org.jgrapes.core.Components;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An observer that watches namespaced resources in a given context and
|
* An observer that watches namespaced resources in a given context and
|
||||||
|
|
@ -48,7 +50,6 @@ public class K8sObserver<O extends KubernetesObject,
|
||||||
ADDED, MODIFIED, DELETED
|
ADDED, MODIFIED, DELETED
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("PMD.FieldNamingConventions")
|
|
||||||
protected final Logger logger = Logger.getLogger(getClass().getName());
|
protected final Logger logger = Logger.getLogger(getClass().getName());
|
||||||
|
|
||||||
protected final K8sClient client;
|
protected final K8sClient client;
|
||||||
|
|
@ -71,9 +72,8 @@ public class K8sObserver<O extends KubernetesObject,
|
||||||
* @param namespace the namespace
|
* @param namespace the namespace
|
||||||
* @param options the options
|
* @param options the options
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
|
@SuppressWarnings({ "PMD.AvoidCatchingThrowable",
|
||||||
"PMD.UseObjectForClearerAPI", "PMD.AvoidCatchingThrowable",
|
"PMD.CognitiveComplexity", "PMD.AvoidCatchingGenericException" })
|
||||||
"PMD.CognitiveComplexity" })
|
|
||||||
public K8sObserver(Class<O> objectClass, Class<L> objectListClass,
|
public K8sObserver(Class<O> objectClass, Class<L> objectListClass,
|
||||||
K8sClient client, APIResource context, String namespace,
|
K8sClient client, APIResource context, String namespace,
|
||||||
ListOptions options) {
|
ListOptions options) {
|
||||||
|
|
@ -85,39 +85,47 @@ public class K8sObserver<O extends KubernetesObject,
|
||||||
api = new GenericKubernetesApi<>(objectClass, objectListClass,
|
api = new GenericKubernetesApi<>(objectClass, objectListClass,
|
||||||
context.getGroup(), context.getPreferredVersion(),
|
context.getGroup(), context.getPreferredVersion(),
|
||||||
context.getResourcePlural(), client);
|
context.getResourcePlural(), client);
|
||||||
thread = new Thread(() -> {
|
thread = (Components.useVirtualThreads() ? Thread.ofVirtual()
|
||||||
try {
|
: Thread.ofPlatform()).unstarted(() -> {
|
||||||
logger.config(() -> "Watching " + context.getResourcePlural()
|
try {
|
||||||
+ " (" + context.getPreferredVersion() + ")"
|
logger.fine(() -> "Observing " + context.getResourcePlural()
|
||||||
+ " in " + namespace);
|
+ " (" + context.getPreferredVersion() + ")"
|
||||||
|
+ Optional.ofNullable(options.getLabelSelector())
|
||||||
|
.map(ls -> " with labels " + ls).orElse("")
|
||||||
|
+ " in " + namespace);
|
||||||
|
|
||||||
// Watch sometimes terminates without apparent reason.
|
// Watch sometimes terminates without apparent reason.
|
||||||
while (!Thread.currentThread().isInterrupted()) {
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
Instant startedAt = Instant.now();
|
Instant startedAt = Instant.now();
|
||||||
try {
|
try {
|
||||||
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
var changed
|
||||||
var changed = api.watch(namespace, options).iterator();
|
= api.watch(namespace, options).iterator();
|
||||||
while (changed.hasNext()) {
|
while (changed.hasNext()) {
|
||||||
handler.accept(client, changed.next());
|
var response = changed.next();
|
||||||
|
logger.fine(() -> "Resource "
|
||||||
|
+ context.getKind() + "/"
|
||||||
|
+ response.object.getMetadata().getName()
|
||||||
|
+ " " + response.type);
|
||||||
|
handler.accept(client, response);
|
||||||
|
}
|
||||||
|
} catch (ApiException | RuntimeException e) {
|
||||||
|
logger.log(Level.FINE, e, () -> "Problem watching"
|
||||||
|
+ " resource " + context.getKind()
|
||||||
|
+ " (will retry): " + e.getMessage());
|
||||||
|
delayRestart(startedAt);
|
||||||
}
|
}
|
||||||
} catch (ApiException e) {
|
}
|
||||||
logger.log(Level.FINE, e, () -> "Problem watching"
|
if (onTerminated != null) {
|
||||||
+ " (will retry): " + e.getMessage());
|
onTerminated.accept(this, null);
|
||||||
delayRestart(startedAt);
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.log(Level.SEVERE, e, () -> "Probem watching: "
|
||||||
|
+ e.getMessage());
|
||||||
|
if (onTerminated != null) {
|
||||||
|
onTerminated.accept(this, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (onTerminated != null) {
|
});
|
||||||
onTerminated.accept(this, null);
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
logger.log(Level.SEVERE, e, () -> "Probem watching: "
|
|
||||||
+ e.getMessage());
|
|
||||||
if (onTerminated != null) {
|
|
||||||
onTerminated.accept(this, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
thread.setDaemon(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
|
@SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
|
||||||
|
|
@ -222,7 +230,6 @@ public class K8sObserver<O extends KubernetesObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("PMD.UseLocaleWithCaseConversions")
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Observer for " + K8s.toString(context) + " " + namespace;
|
return "Observer for " + K8s.toString(context) + " " + namespace;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
* A stub for config maps (v1).
|
* A stub for config maps (v1).
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
|
||||||
public class K8sV1ConfigMapStub
|
public class K8sV1ConfigMapStub
|
||||||
extends K8sGenericStub<V1ConfigMap, V1ConfigMapList> {
|
extends K8sGenericStub<V1ConfigMap, V1ConfigMapList> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import java.util.Optional;
|
||||||
/**
|
/**
|
||||||
* A stub for pods (v1).
|
* A stub for pods (v1).
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
|
||||||
public class K8sV1DeploymentStub
|
public class K8sV1DeploymentStub
|
||||||
extends K8sGenericStub<V1Deployment, V1DeploymentList> {
|
extends K8sGenericStub<V1Deployment, V1DeploymentList> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
* A stub for nodes (v1).
|
* A stub for nodes (v1).
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
|
||||||
public class K8sV1NodeStub extends K8sClusterGenericStub<V1Node, V1NodeList> {
|
public class K8sV1NodeStub extends K8sClusterGenericStub<V1Node, V1NodeList> {
|
||||||
|
|
||||||
public static final APIResource CONTEXT = new APIResource("", List.of("v1"),
|
public static final APIResource CONTEXT = new APIResource("", List.of("v1"),
|
||||||
|
|
@ -74,8 +73,7 @@ public class K8sV1NodeStub extends K8sClusterGenericStub<V1Node, V1NodeList> {
|
||||||
/**
|
/**
|
||||||
* Provide {@link GenericSupplier}.
|
* Provide {@link GenericSupplier}.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.UnusedFormalParameter",
|
@SuppressWarnings({ "PMD.UnusedFormalParameter" })
|
||||||
"PMD.UnusedPrivateMethod" })
|
|
||||||
private static K8sV1NodeStub getGeneric(Class<V1Node> objectClass,
|
private static K8sV1NodeStub getGeneric(Class<V1Node> objectClass,
|
||||||
Class<V1NodeList> objectListClass, K8sClient client,
|
Class<V1NodeList> objectListClass, K8sClient client,
|
||||||
APIResource context, String name) {
|
APIResource context, String name) {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
* A stub for pods (v1).
|
* A stub for pods (v1).
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
|
||||||
public class K8sV1PodStub extends K8sGenericStub<V1Pod, V1PodList> {
|
public class K8sV1PodStub extends K8sGenericStub<V1Pod, V1PodList> {
|
||||||
|
|
||||||
/** The pods' context. */
|
/** The pods' context. */
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2024 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.common;
|
||||||
|
|
||||||
|
import io.kubernetes.client.Discovery.APIResource;
|
||||||
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
|
import io.kubernetes.client.openapi.models.V1PersistentVolumeClaim;
|
||||||
|
import io.kubernetes.client.openapi.models.V1PersistentVolumeClaimList;
|
||||||
|
import io.kubernetes.client.util.generic.options.ListOptions;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stub for pods (v1).
|
||||||
|
*/
|
||||||
|
public class K8sV1PvcStub extends
|
||||||
|
K8sGenericStub<V1PersistentVolumeClaim, V1PersistentVolumeClaimList> {
|
||||||
|
|
||||||
|
/** The pods' context. */
|
||||||
|
public static final APIResource CONTEXT
|
||||||
|
= new APIResource("", List.of("v1"), "v1", "PersistentVolumeClaim",
|
||||||
|
true, "persistentvolumeclaims", "persistentvolumeclaim");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new stub.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
*/
|
||||||
|
protected K8sV1PvcStub(K8sClient client, String namespace, String name) {
|
||||||
|
super(V1PersistentVolumeClaim.class, V1PersistentVolumeClaimList.class,
|
||||||
|
client, CONTEXT, namespace, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the stub for the given namespace and name.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param name the name
|
||||||
|
* @return the kpod stub
|
||||||
|
*/
|
||||||
|
public static K8sV1PvcStub get(K8sClient client, String namespace,
|
||||||
|
String name) {
|
||||||
|
return new K8sV1PvcStub(client, namespace, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the stubs for the objects in the given namespace that match
|
||||||
|
* the criteria from the given options.
|
||||||
|
*
|
||||||
|
* @param client the client
|
||||||
|
* @param namespace the namespace
|
||||||
|
* @param options the options
|
||||||
|
* @return the collection
|
||||||
|
* @throws ApiException the api exception
|
||||||
|
*/
|
||||||
|
public static Collection<K8sV1PvcStub> list(K8sClient client,
|
||||||
|
String namespace, ListOptions options) throws ApiException {
|
||||||
|
return K8sGenericStub.list(V1PersistentVolumeClaim.class,
|
||||||
|
V1PersistentVolumeClaimList.class, client, CONTEXT, namespace,
|
||||||
|
options, (clnt, nscp, name) -> new K8sV1PvcStub(clnt, nscp, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,7 +29,6 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
* A stub for secrets (v1).
|
* A stub for secrets (v1).
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
|
||||||
public class K8sV1SecretStub extends K8sGenericStub<V1Secret, V1SecretList> {
|
public class K8sV1SecretStub extends K8sGenericStub<V1Secret, V1SecretList> {
|
||||||
|
|
||||||
public static final APIResource CONTEXT = new APIResource("", List.of("v1"),
|
public static final APIResource CONTEXT = new APIResource("", List.of("v1"),
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
* A stub for secrets (v1).
|
* A stub for secrets (v1).
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
|
||||||
public class K8sV1ServiceStub extends K8sGenericStub<V1Service, V1ServiceList> {
|
public class K8sV1ServiceStub extends K8sGenericStub<V1Service, V1ServiceList> {
|
||||||
|
|
||||||
public static final APIResource CONTEXT = new APIResource("", List.of("v1"),
|
public static final APIResource CONTEXT = new APIResource("", List.of("v1"),
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
* A stub for stateful sets (v1).
|
* A stub for stateful sets (v1).
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
|
||||||
public class K8sV1StatefulSetStub
|
public class K8sV1StatefulSetStub
|
||||||
extends K8sGenericStub<V1StatefulSet, V1StatefulSetList> {
|
extends K8sGenericStub<V1StatefulSet, V1StatefulSetList> {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,499 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2025 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.common;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kubernetes.client.openapi.JSON;
|
||||||
|
import io.kubernetes.client.openapi.models.V1Condition;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.jdrupes.vmoperator.common.Constants.Status;
|
||||||
|
import org.jdrupes.vmoperator.common.Constants.Status.Condition;
|
||||||
|
import org.jdrupes.vmoperator.common.Constants.Status.Condition.Reason;
|
||||||
|
import org.jdrupes.vmoperator.util.DataPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a VM definition.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "PMD.DataClass", "PMD.TooManyMethods" })
|
||||||
|
public class VmDefinition extends K8sDynamicModel {
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unused" })
|
||||||
|
private static final Logger logger
|
||||||
|
= Logger.getLogger(VmDefinition.class.getName());
|
||||||
|
@SuppressWarnings("PMD.FieldNamingConventions")
|
||||||
|
private static final Gson gson = new JSON().getGson();
|
||||||
|
@SuppressWarnings("PMD.FieldNamingConventions")
|
||||||
|
private static final ObjectMapper objectMapper
|
||||||
|
= new ObjectMapper().registerModule(new JavaTimeModule());
|
||||||
|
|
||||||
|
private final Model model;
|
||||||
|
private VmExtraData extraData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The VM state from the VM definition.
|
||||||
|
*/
|
||||||
|
public enum RequestedVmState {
|
||||||
|
STOPPED, RUNNING
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permissions for accessing and manipulating the VM.
|
||||||
|
*/
|
||||||
|
public enum Permission {
|
||||||
|
START("start"), STOP("stop"), RESET("reset"),
|
||||||
|
ACCESS_CONSOLE("accessConsole"), TAKE_CONSOLE("takeConsole");
|
||||||
|
|
||||||
|
@SuppressWarnings("PMD.UseConcurrentHashMap")
|
||||||
|
private static Map<String, Permission> reprs = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (var value : EnumSet.allOf(Permission.class)) {
|
||||||
|
reprs.put(value.repr, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String repr;
|
||||||
|
|
||||||
|
Permission(String repr) {
|
||||||
|
this.repr = repr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create permission from representation in CRD.
|
||||||
|
*
|
||||||
|
* @param value the value
|
||||||
|
* @return the permission
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
|
||||||
|
public static Set<Permission> parse(String value) {
|
||||||
|
if ("*".equals(value)) {
|
||||||
|
return EnumSet.allOf(Permission.class);
|
||||||
|
}
|
||||||
|
return Set.of(reprs.get(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To string.
|
||||||
|
*
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return repr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permissions granted to a user or role.
|
||||||
|
*
|
||||||
|
* @param user the user
|
||||||
|
* @param role the role
|
||||||
|
* @param may the may
|
||||||
|
*/
|
||||||
|
public record Grant(String user, String role, Set<Permission> may) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To string.
|
||||||
|
*
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
if (user != null) {
|
||||||
|
builder.append("User ").append(user);
|
||||||
|
} else {
|
||||||
|
builder.append("Role ").append(role);
|
||||||
|
}
|
||||||
|
builder.append(" may=").append(may).append(']');
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The assignment information.
|
||||||
|
*
|
||||||
|
* @param pool the pool
|
||||||
|
* @param user the user
|
||||||
|
* @param lastUsed the last used
|
||||||
|
*/
|
||||||
|
public record Assignment(String pool, String user, Instant lastUsed) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new vm definition.
|
||||||
|
*
|
||||||
|
* @param delegate the delegate
|
||||||
|
* @param json the json
|
||||||
|
*/
|
||||||
|
public VmDefinition(Gson delegate, JsonObject json) {
|
||||||
|
super(delegate, json);
|
||||||
|
model = gson.fromJson(json, Model.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the spec.
|
||||||
|
*
|
||||||
|
* @return the spec
|
||||||
|
*/
|
||||||
|
public Map<String, Object> spec() {
|
||||||
|
return model.getSpec();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a value from the spec using {@link DataPath#get}.
|
||||||
|
*
|
||||||
|
* @param <T> the generic type
|
||||||
|
* @param selectors the selectors
|
||||||
|
* @return the value, if found
|
||||||
|
*/
|
||||||
|
public <T> Optional<T> fromSpec(Object... selectors) {
|
||||||
|
return DataPath.get(spec(), selectors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pools that this VM belongs to.
|
||||||
|
*
|
||||||
|
* @return the list
|
||||||
|
*/
|
||||||
|
public List<String> pools() {
|
||||||
|
return this.<List<String>> fromSpec("pools")
|
||||||
|
.orElse(Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a value from the `spec().get("vm")` using {@link DataPath#get}.
|
||||||
|
*
|
||||||
|
* @param <T> the generic type
|
||||||
|
* @param selectors the selectors
|
||||||
|
* @return the value, if found
|
||||||
|
*/
|
||||||
|
public <T> Optional<T> fromVm(Object... selectors) {
|
||||||
|
return DataPath.get(spec(), "vm")
|
||||||
|
.flatMap(vm -> DataPath.get(vm, selectors));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the status.
|
||||||
|
*
|
||||||
|
* @return the status
|
||||||
|
*/
|
||||||
|
public Map<String, Object> status() {
|
||||||
|
return model.getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a value from the status using {@link DataPath#get}.
|
||||||
|
*
|
||||||
|
* @param <T> the generic type
|
||||||
|
* @param selectors the selectors
|
||||||
|
* @return the value, if found
|
||||||
|
*/
|
||||||
|
public <T> Optional<T> fromStatus(Object... selectors) {
|
||||||
|
return DataPath.get(status(), selectors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The assignment information.
|
||||||
|
*
|
||||||
|
* @return the optional
|
||||||
|
*/
|
||||||
|
public Optional<Assignment> assignment() {
|
||||||
|
return this.<Map<String, Object>> fromStatus(Status.ASSIGNMENT)
|
||||||
|
.filter(m -> !m.isEmpty()).map(a -> new Assignment(
|
||||||
|
a.get("pool").toString(), a.get("user").toString(),
|
||||||
|
Instant.parse(a.get("lastUsed").toString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a condition from the status.
|
||||||
|
*
|
||||||
|
* @param name the condition's name
|
||||||
|
* @return the status, if the condition is defined
|
||||||
|
*/
|
||||||
|
public Optional<V1Condition> condition(String name) {
|
||||||
|
return this.<List<Map<String, Object>>> fromStatus("conditions")
|
||||||
|
.orElse(Collections.emptyList()).stream()
|
||||||
|
.filter(cond -> DataPath.get(cond, "type")
|
||||||
|
.map(name::equals).orElse(false))
|
||||||
|
.findFirst()
|
||||||
|
.map(cond -> objectMapper.convertValue(cond, V1Condition.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a condition's status.
|
||||||
|
*
|
||||||
|
* @param name the condition's name
|
||||||
|
* @return the status, if the condition is defined
|
||||||
|
*/
|
||||||
|
public Optional<Boolean> conditionStatus(String name) {
|
||||||
|
return this.<List<Map<String, Object>>> fromStatus("conditions")
|
||||||
|
.orElse(Collections.emptyList()).stream()
|
||||||
|
.filter(cond -> DataPath.get(cond, "type")
|
||||||
|
.map(name::equals).orElse(false))
|
||||||
|
.findFirst().map(cond -> DataPath.get(cond, "status")
|
||||||
|
.map("True"::equals).orElse(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the console is in use.
|
||||||
|
*
|
||||||
|
* @return true, if successful
|
||||||
|
*/
|
||||||
|
public boolean consoleConnected() {
|
||||||
|
return conditionStatus("ConsoleConnected").orElse(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the last known console user.
|
||||||
|
*
|
||||||
|
* @return the optional
|
||||||
|
*/
|
||||||
|
public Optional<String> consoleUser() {
|
||||||
|
return this.<String> fromStatus(Status.CONSOLE_USER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set extra data (unknown to kubernetes).
|
||||||
|
* @return the VM definition
|
||||||
|
*/
|
||||||
|
/* default */ VmDefinition extra(VmExtraData extraData) {
|
||||||
|
this.extraData = extraData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the extra data.
|
||||||
|
*
|
||||||
|
* @return the data
|
||||||
|
*/
|
||||||
|
public VmExtraData extra() {
|
||||||
|
return extraData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the definition's name.
|
||||||
|
*
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
public String name() {
|
||||||
|
return metadata().getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the definition's namespace.
|
||||||
|
*
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
public String namespace() {
|
||||||
|
return metadata().getNamespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the requested VM state.
|
||||||
|
*
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
public RequestedVmState vmState() {
|
||||||
|
return fromVm("state")
|
||||||
|
.map(s -> "Running".equals(s) ? RequestedVmState.RUNNING
|
||||||
|
: RequestedVmState.STOPPED)
|
||||||
|
.orElse(RequestedVmState.STOPPED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect all permissions for the given user with the given roles.
|
||||||
|
* If permission "takeConsole" is granted, the result will also
|
||||||
|
* contain "accessConsole" to simplify checks.
|
||||||
|
*
|
||||||
|
* @param user the user
|
||||||
|
* @param roles the roles
|
||||||
|
* @return the sets the
|
||||||
|
*/
|
||||||
|
public Set<Permission> permissionsFor(String user,
|
||||||
|
Collection<String> roles) {
|
||||||
|
var result = this.<List<Map<String, Object>>> fromSpec("permissions")
|
||||||
|
.orElse(Collections.emptyList()).stream()
|
||||||
|
.filter(p -> DataPath.get(p, "user").map(u -> u.equals(user))
|
||||||
|
.orElse(false)
|
||||||
|
|| DataPath.get(p, "role").map(roles::contains).orElse(false))
|
||||||
|
.map(p -> DataPath.<List<String>> get(p, "may")
|
||||||
|
.orElse(Collections.emptyList()).stream())
|
||||||
|
.flatMap(Function.identity())
|
||||||
|
.map(Permission::parse).map(Set::stream)
|
||||||
|
.flatMap(Function.identity())
|
||||||
|
.collect(Collectors.toCollection(HashSet::new));
|
||||||
|
|
||||||
|
// Take console implies access console, simplify checks
|
||||||
|
if (result.contains(Permission.TAKE_CONSOLE)) {
|
||||||
|
result.add(Permission.ACCESS_CONSOLE);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the console is accessible. Always returns `true` if
|
||||||
|
* the VM is running and the permissions allow taking over the
|
||||||
|
* console. Else, returns `true` if
|
||||||
|
*
|
||||||
|
* * the permissions allow access to the console and
|
||||||
|
*
|
||||||
|
* * the VM is running and
|
||||||
|
*
|
||||||
|
* * the console is currently unused or used by the given user and
|
||||||
|
*
|
||||||
|
* * if user login is requested, the given user is logged in.
|
||||||
|
*
|
||||||
|
* @param user the user
|
||||||
|
* @param permissions the permissions
|
||||||
|
* @return true, if successful
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.SimplifyBooleanReturns")
|
||||||
|
public boolean consoleAccessible(String user, Set<Permission> permissions) {
|
||||||
|
// Basic checks
|
||||||
|
if (!conditionStatus(Condition.RUNNING).orElse(false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (permissions.contains(Permission.TAKE_CONSOLE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!permissions.contains(Permission.ACCESS_CONSOLE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the console is in use by another user, deny access
|
||||||
|
if (conditionStatus(Condition.CONSOLE_CONNECTED).orElse(false)
|
||||||
|
&& !consoleUser().map(cu -> cu.equals(user)).orElse(false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no login is requested, allow access, else check if user matches
|
||||||
|
if (condition(Condition.USER_LOGGED_IN).map(V1Condition::getReason)
|
||||||
|
.map(r -> Reason.NOT_REQUESTED.equals(r)).orElse(false)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return user.equals(status().get(Status.LOGGED_IN_USER));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the display password serial.
|
||||||
|
*
|
||||||
|
* @return the optional
|
||||||
|
*/
|
||||||
|
public Optional<Long> displayPasswordSerial() {
|
||||||
|
return this.<Number> fromStatus(Status.DISPLAY_PASSWORD_SERIAL)
|
||||||
|
.map(Number::longValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hash code.
|
||||||
|
*
|
||||||
|
* @return the int
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(metadata().getNamespace(), metadata().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equals.
|
||||||
|
*
|
||||||
|
* @param obj the obj
|
||||||
|
* @return true, if successful
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
VmDefinition other = (VmDefinition) obj;
|
||||||
|
return Objects.equals(metadata().getNamespace(),
|
||||||
|
other.metadata().getNamespace())
|
||||||
|
&& Objects.equals(metadata().getName(), other.metadata().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class Model.
|
||||||
|
*/
|
||||||
|
public static class Model {
|
||||||
|
|
||||||
|
private Map<String, Object> spec;
|
||||||
|
private Map<String, Object> status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the spec.
|
||||||
|
*
|
||||||
|
* @return the spec
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getSpec() {
|
||||||
|
return spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the spec.
|
||||||
|
*
|
||||||
|
* @param spec the spec to set
|
||||||
|
*/
|
||||||
|
public void setSpec(Map<String, Object> spec) {
|
||||||
|
this.spec = spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the status.
|
||||||
|
*
|
||||||
|
* @return the status
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the status.
|
||||||
|
*
|
||||||
|
* @param status the status to set
|
||||||
|
*/
|
||||||
|
public void setStatus(Map<String, Object> status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
/*
|
|
||||||
* VM-Operator
|
|
||||||
* Copyright (C) 2024 Michael N. Lipp
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jdrupes.vmoperator.common;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import org.jdrupes.vmoperator.util.GsonPtr;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a VM definition.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("PMD.DataClass")
|
|
||||||
public class VmDefinitionModel extends K8sDynamicModel {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Permissions for accessing and manipulating the VM.
|
|
||||||
*/
|
|
||||||
public enum Permission {
|
|
||||||
START("start"), STOP("stop"), ACCESS_CONSOLE("accessConsole");
|
|
||||||
|
|
||||||
@SuppressWarnings("PMD.UseConcurrentHashMap")
|
|
||||||
private static Map<String, Permission> reprs = new HashMap<>();
|
|
||||||
|
|
||||||
static {
|
|
||||||
for (var value : EnumSet.allOf(Permission.class)) {
|
|
||||||
reprs.put(value.repr, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String repr;
|
|
||||||
|
|
||||||
Permission(String repr) {
|
|
||||||
this.repr = repr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create permission from representation in CRD.
|
|
||||||
*
|
|
||||||
* @param value the value
|
|
||||||
* @return the permission
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
|
|
||||||
public static Set<Permission> parse(String value) {
|
|
||||||
if ("*".equals(value)) {
|
|
||||||
return EnumSet.allOf(Permission.class);
|
|
||||||
}
|
|
||||||
return Set.of(reprs.get(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return repr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates a new model from the JSON representation.
|
|
||||||
*
|
|
||||||
* @param delegate the gson instance to use for extracting structured data
|
|
||||||
* @param json the JSON
|
|
||||||
*/
|
|
||||||
public VmDefinitionModel(Gson delegate, JsonObject json) {
|
|
||||||
super(delegate, json);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collect all permissions for the given user with the given roles.
|
|
||||||
*
|
|
||||||
* @param user the user
|
|
||||||
* @param roles the roles
|
|
||||||
* @return the sets the
|
|
||||||
*/
|
|
||||||
public Set<Permission> permissionsFor(String user,
|
|
||||||
Collection<String> roles) {
|
|
||||||
return GsonPtr.to(data())
|
|
||||||
.getAsListOf(JsonObject.class, "spec", "permissions")
|
|
||||||
.stream().filter(p -> GsonPtr.to(p).getAsString("user")
|
|
||||||
.map(u -> u.equals(user)).orElse(false)
|
|
||||||
|| GsonPtr.to(p).getAsString("role").map(roles::contains)
|
|
||||||
.orElse(false))
|
|
||||||
.map(p -> GsonPtr.to(p).getAsListOf(JsonPrimitive.class, "may")
|
|
||||||
.stream())
|
|
||||||
.flatMap(Function.identity()).map(p -> p.getAsString())
|
|
||||||
.map(Permission::parse).map(Set::stream)
|
|
||||||
.flatMap(Function.identity()).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the display password serial.
|
|
||||||
*
|
|
||||||
* @return the optional
|
|
||||||
*/
|
|
||||||
public Optional<Long> displayPasswordSerial() {
|
|
||||||
return GsonPtr.to(status())
|
|
||||||
.get(JsonPrimitive.class, "displayPasswordSerial")
|
|
||||||
.map(JsonPrimitive::getAsLong);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -31,12 +31,11 @@ import java.util.Collection;
|
||||||
* state and can therefore be used for any kind of object, especially
|
* state and can therefore be used for any kind of object, especially
|
||||||
* custom objects.
|
* custom objects.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
|
||||||
public class VmDefinitionStub
|
public class VmDefinitionStub
|
||||||
extends K8sDynamicStubBase<VmDefinitionModel, VmDefinitionModels> {
|
extends K8sDynamicStubBase<VmDefinition, VmDefinitions> {
|
||||||
|
|
||||||
private static DynamicTypeAdapterFactory<VmDefinitionModel,
|
private static DynamicTypeAdapterFactory<VmDefinition,
|
||||||
VmDefinitionModels> taf = new VmDefintionModelTypeAdapterFactory();
|
VmDefinitions> taf = new VmDefintionModelTypeAdapterFactory();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new stub for VM defintions.
|
* Instantiates a new stub for VM defintions.
|
||||||
|
|
@ -48,7 +47,7 @@ public class VmDefinitionStub
|
||||||
*/
|
*/
|
||||||
public VmDefinitionStub(K8sClient client, APIResource context,
|
public VmDefinitionStub(K8sClient client, APIResource context,
|
||||||
String namespace, String name) {
|
String namespace, String name) {
|
||||||
super(VmDefinitionModel.class, VmDefinitionModels.class, taf, client,
|
super(VmDefinition.class, VmDefinitions.class, taf, client,
|
||||||
context, namespace, name);
|
context, namespace, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,8 +63,6 @@ public class VmDefinitionStub
|
||||||
* @return the stub if the object exists
|
* @return the stub if the object exists
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
|
|
||||||
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
|
|
||||||
public static VmDefinitionStub get(K8sClient client,
|
public static VmDefinitionStub get(K8sClient client,
|
||||||
GroupVersionKind gvk, String namespace, String name)
|
GroupVersionKind gvk, String namespace, String name)
|
||||||
throws ApiException {
|
throws ApiException {
|
||||||
|
|
@ -83,8 +80,6 @@ public class VmDefinitionStub
|
||||||
* @return the stub if the object exists
|
* @return the stub if the object exists
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.AvoidBranchingStatementAsLastInLoop",
|
|
||||||
"PMD.AvoidInstantiatingObjectsInLoops", "PMD.UseObjectForClearerAPI" })
|
|
||||||
public static VmDefinitionStub get(K8sClient client,
|
public static VmDefinitionStub get(K8sClient client,
|
||||||
APIResource context, String namespace, String name) {
|
APIResource context, String namespace, String name) {
|
||||||
return new VmDefinitionStub(client, context, namespace, name);
|
return new VmDefinitionStub(client, context, namespace, name);
|
||||||
|
|
@ -101,10 +96,10 @@ public class VmDefinitionStub
|
||||||
*/
|
*/
|
||||||
public static VmDefinitionStub createFromYaml(K8sClient client,
|
public static VmDefinitionStub createFromYaml(K8sClient client,
|
||||||
APIResource context, Reader yaml) throws ApiException {
|
APIResource context, Reader yaml) throws ApiException {
|
||||||
var model = new VmDefinitionModel(client.getJSON().getGson(),
|
var model = new VmDefinition(client.getJSON().getGson(),
|
||||||
K8s.yamlToJson(client, yaml));
|
K8s.yamlToJson(client, yaml));
|
||||||
return K8sGenericStub.create(VmDefinitionModel.class,
|
return K8sGenericStub.create(VmDefinition.class,
|
||||||
VmDefinitionModels.class, client, context, model,
|
VmDefinitions.class, client, context, model,
|
||||||
(c, ns, n) -> new VmDefinitionStub(c, context, ns, n));
|
(c, ns, n) -> new VmDefinitionStub(c, context, ns, n));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,8 +116,8 @@ public class VmDefinitionStub
|
||||||
public static Collection<VmDefinitionStub> list(K8sClient client,
|
public static Collection<VmDefinitionStub> list(K8sClient client,
|
||||||
APIResource context, String namespace, ListOptions options)
|
APIResource context, String namespace, ListOptions options)
|
||||||
throws ApiException {
|
throws ApiException {
|
||||||
return K8sGenericStub.list(VmDefinitionModel.class,
|
return K8sGenericStub.list(VmDefinition.class,
|
||||||
VmDefinitionModels.class, client, context, namespace, options,
|
VmDefinitions.class, client, context, namespace, options,
|
||||||
(c, ns, n) -> new VmDefinitionStub(c, context, ns, n));
|
(c, ns, n) -> new VmDefinitionStub(c, context, ns, n));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,13 +139,13 @@ public class VmDefinitionStub
|
||||||
* A factory for creating VmDefinitionModel(s) objects.
|
* A factory for creating VmDefinitionModel(s) objects.
|
||||||
*/
|
*/
|
||||||
public static class VmDefintionModelTypeAdapterFactory extends
|
public static class VmDefintionModelTypeAdapterFactory extends
|
||||||
DynamicTypeAdapterFactory<VmDefinitionModel, VmDefinitionModels> {
|
DynamicTypeAdapterFactory<VmDefinition, VmDefinitions> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new dynamic model type adapter factory.
|
* Instantiates a new dynamic model type adapter factory.
|
||||||
*/
|
*/
|
||||||
public VmDefintionModelTypeAdapterFactory() {
|
public VmDefintionModelTypeAdapterFactory() {
|
||||||
super(VmDefinitionModel.class, VmDefinitionModels.class);
|
super(VmDefinition.class, VmDefinitions.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,10 @@ import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a list of {@link VmDefinitionModel}s.
|
* Represents a list of {@link VmDefinition}s.
|
||||||
*/
|
*/
|
||||||
public class VmDefinitionModels
|
public class VmDefinitions
|
||||||
extends K8sDynamicModelsBase<VmDefinitionModel> {
|
extends K8sDynamicModelsBase<VmDefinition> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the object list using the given JSON data.
|
* Initialize the object list using the given JSON data.
|
||||||
|
|
@ -33,7 +33,7 @@ public class VmDefinitionModels
|
||||||
* @param delegate the gson instance to use for extracting structured data
|
* @param delegate the gson instance to use for extracting structured data
|
||||||
* @param data the data
|
* @param data the data
|
||||||
*/
|
*/
|
||||||
public VmDefinitionModels(Gson delegate, JsonObject data) {
|
public VmDefinitions(Gson delegate, JsonObject data) {
|
||||||
super(VmDefinitionModel.class, delegate, data);
|
super(VmDefinition.class, delegate, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2025 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.common;
|
||||||
|
|
||||||
|
import io.kubernetes.client.util.Strings;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents internally used dynamic data associated with a
|
||||||
|
* {@link VmDefinition}.
|
||||||
|
*/
|
||||||
|
public class VmExtraData {
|
||||||
|
|
||||||
|
private static final Logger logger
|
||||||
|
= Logger.getLogger(VmExtraData.class.getName());
|
||||||
|
|
||||||
|
private final VmDefinition vmDef;
|
||||||
|
private String nodeName = "";
|
||||||
|
private List<String> nodeAddresses = Collections.emptyList();
|
||||||
|
private long resetCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new instance.
|
||||||
|
*
|
||||||
|
* @param vmDef the VM definition
|
||||||
|
*/
|
||||||
|
public VmExtraData(VmDefinition vmDef) {
|
||||||
|
this.vmDef = vmDef;
|
||||||
|
vmDef.extra(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the node info.
|
||||||
|
*
|
||||||
|
* @param name the name
|
||||||
|
* @param addresses the addresses
|
||||||
|
* @return the VM extra data
|
||||||
|
*/
|
||||||
|
public VmExtraData nodeInfo(String name, List<String> addresses) {
|
||||||
|
nodeName = name;
|
||||||
|
nodeAddresses = addresses;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the node name.
|
||||||
|
*
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
public String nodeName() {
|
||||||
|
return nodeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the node addresses.
|
||||||
|
*
|
||||||
|
* @return the nodeAddresses
|
||||||
|
*/
|
||||||
|
public List<String> nodeAddresses() {
|
||||||
|
return nodeAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the reset count.
|
||||||
|
*
|
||||||
|
* @param resetCount the reset count
|
||||||
|
* @return the vm extra data
|
||||||
|
*/
|
||||||
|
public VmExtraData resetCount(long resetCount) {
|
||||||
|
this.resetCount = resetCount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the reset count.
|
||||||
|
*
|
||||||
|
* @return the long
|
||||||
|
*/
|
||||||
|
public long resetCount() {
|
||||||
|
return resetCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a connection file.
|
||||||
|
*
|
||||||
|
* @param password the password
|
||||||
|
* @param preferredIpVersion the preferred IP version
|
||||||
|
* @param deleteConnectionFile the delete connection file
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
public Optional<String> connectionFile(String password,
|
||||||
|
Class<?> preferredIpVersion, boolean deleteConnectionFile) {
|
||||||
|
var addr = displayIp(preferredIpVersion);
|
||||||
|
if (addr.isEmpty()) {
|
||||||
|
logger
|
||||||
|
.severe(() -> "Failed to find display IP for " + vmDef.name());
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
var port = vmDef.<Number> fromVm("display", "spice", "port")
|
||||||
|
.map(Number::longValue);
|
||||||
|
if (port.isEmpty()) {
|
||||||
|
logger
|
||||||
|
.severe(() -> "No port defined for display of " + vmDef.name());
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
StringBuffer data = new StringBuffer(100)
|
||||||
|
.append("[virt-viewer]\ntype=spice\nhost=")
|
||||||
|
.append(addr.get().getHostAddress()).append("\nport=")
|
||||||
|
.append(port.get().toString())
|
||||||
|
.append('\n');
|
||||||
|
if (password != null) {
|
||||||
|
data.append("password=").append(password).append('\n');
|
||||||
|
}
|
||||||
|
vmDef.<String> fromVm("display", "spice", "proxyUrl")
|
||||||
|
.ifPresent(u -> {
|
||||||
|
if (!Strings.isNullOrEmpty(u)) {
|
||||||
|
data.append("proxy=").append(u).append('\n');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (deleteConnectionFile) {
|
||||||
|
data.append("delete-this-file=1\n");
|
||||||
|
}
|
||||||
|
return Optional.of(data.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<InetAddress> displayIp(Class<?> preferredIpVersion) {
|
||||||
|
Optional<String> server = vmDef.fromVm("display", "spice", "server");
|
||||||
|
if (server.isPresent()) {
|
||||||
|
var srv = server.get();
|
||||||
|
try {
|
||||||
|
var addr = InetAddress.getByName(srv);
|
||||||
|
logger.fine(() -> "Using IP address from CRD for "
|
||||||
|
+ vmDef.metadata().getName() + ": " + addr);
|
||||||
|
return Optional.of(addr);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
logger.log(Level.SEVERE, e, () -> "Invalid server address "
|
||||||
|
+ srv + ": " + e.getMessage());
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var addrs = nodeAddresses.stream().map(a -> {
|
||||||
|
try {
|
||||||
|
return InetAddress.getByName(a);
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
logger.warning(() -> "Invalid IP address: " + a);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}).filter(Objects::nonNull).toList();
|
||||||
|
logger.fine(
|
||||||
|
() -> "Known IP addresses for " + vmDef.name() + ": " + addrs);
|
||||||
|
return addrs.stream()
|
||||||
|
.filter(a -> preferredIpVersion.isAssignableFrom(a.getClass()))
|
||||||
|
.findFirst().or(() -> addrs.stream().findFirst());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,226 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2024 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.common;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.jdrupes.vmoperator.common.VmDefinition.Assignment;
|
||||||
|
import org.jdrupes.vmoperator.common.VmDefinition.Grant;
|
||||||
|
import org.jdrupes.vmoperator.common.VmDefinition.Permission;
|
||||||
|
import org.jdrupes.vmoperator.util.DataPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a VM pool.
|
||||||
|
*/
|
||||||
|
public class VmPool {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private String retention;
|
||||||
|
private boolean loginOnAssignment;
|
||||||
|
private boolean defined;
|
||||||
|
private List<Grant> permissions = Collections.emptyList();
|
||||||
|
private final Set<String> vms
|
||||||
|
= Collections.synchronizedSet(new HashSet<>());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new vm pool.
|
||||||
|
*
|
||||||
|
* @param name the name
|
||||||
|
*/
|
||||||
|
public VmPool(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the properties of a provisionally created pool from
|
||||||
|
* the definition.
|
||||||
|
*
|
||||||
|
* @param definition the definition
|
||||||
|
*/
|
||||||
|
public void defineFrom(VmPool definition) {
|
||||||
|
retention = definition.retention();
|
||||||
|
permissions = definition.permissions();
|
||||||
|
loginOnAssignment = definition.loginOnAssignment();
|
||||||
|
defined = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name.
|
||||||
|
*
|
||||||
|
* @return the name
|
||||||
|
*/
|
||||||
|
public String name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if is login on assignment.
|
||||||
|
*
|
||||||
|
* @return the loginOnAssignment
|
||||||
|
*/
|
||||||
|
public boolean loginOnAssignment() {
|
||||||
|
return loginOnAssignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if is defined.
|
||||||
|
*
|
||||||
|
* @return the result
|
||||||
|
*/
|
||||||
|
public boolean isDefined() {
|
||||||
|
return defined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marks the pool as undefined.
|
||||||
|
*/
|
||||||
|
public void setUndefined() {
|
||||||
|
defined = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the retention.
|
||||||
|
*
|
||||||
|
* @return the retention
|
||||||
|
*/
|
||||||
|
public String retention() {
|
||||||
|
return retention;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permissions granted for a VM from the pool.
|
||||||
|
*
|
||||||
|
* @return the permissions
|
||||||
|
*/
|
||||||
|
public List<Grant> permissions() {
|
||||||
|
return permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the VM names.
|
||||||
|
*
|
||||||
|
* @return the vms
|
||||||
|
*/
|
||||||
|
public Set<String> vms() {
|
||||||
|
return vms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect all permissions for the given user with the given roles.
|
||||||
|
*
|
||||||
|
* @param user the user
|
||||||
|
* @param roles the roles
|
||||||
|
* @return the sets the
|
||||||
|
*/
|
||||||
|
public Set<Permission> permissionsFor(String user,
|
||||||
|
Collection<String> roles) {
|
||||||
|
return permissions.stream()
|
||||||
|
.filter(g -> DataPath.get(g, "user").map(u -> u.equals(user))
|
||||||
|
.orElse(false)
|
||||||
|
|| DataPath.get(g, "role").map(roles::contains).orElse(false))
|
||||||
|
.map(g -> DataPath.<Set<Permission>> get(g, "may")
|
||||||
|
.orElse(Collections.emptySet()).stream())
|
||||||
|
.flatMap(Function.identity()).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given VM belongs to the pool and is not in use.
|
||||||
|
*
|
||||||
|
* @param vmDef the vm def
|
||||||
|
* @return true, if is assignable
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("PMD.SimplifyBooleanReturns")
|
||||||
|
public boolean isAssignable(VmDefinition vmDef) {
|
||||||
|
// Check if the VM is in the pool
|
||||||
|
if (!vmDef.pools().contains(name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the VM is not in use
|
||||||
|
if (vmDef.consoleConnected()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not assigned, it's usable
|
||||||
|
if (vmDef.assignment().isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it is to be retained
|
||||||
|
if (vmDef.assignment().map(Assignment::lastUsed).map(this::retainUntil)
|
||||||
|
.map(ru -> Instant.now().isBefore(ru)).orElse(false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional check in case lastUsed has not been updated
|
||||||
|
// by PoolMonitor#onVmResourceChanged() yet ("race condition")
|
||||||
|
if (vmDef.condition("ConsoleConnected")
|
||||||
|
.map(cc -> cc.getLastTransitionTime().toInstant())
|
||||||
|
.map(this::retainUntil)
|
||||||
|
.map(ru -> Instant.now().isBefore(ru)).orElse(false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the instant until which an assignment should be retained.
|
||||||
|
*
|
||||||
|
* @param lastUsed the last used
|
||||||
|
* @return the instant
|
||||||
|
*/
|
||||||
|
public Instant retainUntil(Instant lastUsed) {
|
||||||
|
if (retention.startsWith("P")) {
|
||||||
|
return lastUsed.plus(Duration.parse(retention));
|
||||||
|
}
|
||||||
|
return Instant.parse(retention);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To string.
|
||||||
|
*
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings({ "PMD.AvoidLiteralsInIfCondition",
|
||||||
|
"PMD.AvoidSynchronizedStatement" })
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder(50);
|
||||||
|
builder.append("VmPool [name=").append(name).append(", permissions=")
|
||||||
|
.append(permissions).append(", vms=");
|
||||||
|
if (vms.size() <= 3) {
|
||||||
|
builder.append(vms);
|
||||||
|
} else {
|
||||||
|
synchronized (vms) {
|
||||||
|
builder.append('[').append(vms.stream().limit(3)
|
||||||
|
.map(s -> s + ",").collect(Collectors.joining()))
|
||||||
|
.append("...]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
builder.append(']');
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,5 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api 'org.jgrapes:org.jgrapes.core:[1.19.0,2)'
|
|
||||||
api project(':org.jdrupes.vmoperator.common')
|
api project(':org.jdrupes.vmoperator.common')
|
||||||
api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:[2.16.1,3]'
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,45 +18,43 @@
|
||||||
|
|
||||||
package org.jdrupes.vmoperator.manager.events;
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
import java.util.Optional;
|
import org.jdrupes.vmoperator.manager.events.GetVms.VmData;
|
||||||
import org.jdrupes.vmoperator.common.VmDefinitionModel;
|
|
||||||
import org.jgrapes.core.Event;
|
import org.jgrapes.core.Event;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current display secret and optionally updates it.
|
* Assign a VM from a pool to a user.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataClass")
|
public class AssignVm extends Event<VmData> {
|
||||||
public class GetDisplayPassword extends Event<String> {
|
|
||||||
|
|
||||||
private final VmDefinitionModel vmDef;
|
private final String fromPool;
|
||||||
|
private final String toUser;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new returns the display secret.
|
* Instantiates a new event.
|
||||||
*
|
*
|
||||||
* @param vmDef the vm name
|
* @param fromPool the from pool
|
||||||
|
* @param toUser the to user
|
||||||
*/
|
*/
|
||||||
public GetDisplayPassword(VmDefinitionModel vmDef) {
|
public AssignVm(String fromPool, String toUser) {
|
||||||
this.vmDef = vmDef;
|
this.fromPool = fromPool;
|
||||||
|
this.toUser = toUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the vm definition.
|
* Gets the pool to assign from.
|
||||||
*
|
*
|
||||||
* @return the vm definition
|
* @return the pool
|
||||||
*/
|
*/
|
||||||
public VmDefinitionModel vmDefinition() {
|
public String fromPool() {
|
||||||
return vmDef;
|
return fromPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the password. May only be called when the event is completed.
|
* Gets the user to assign to.
|
||||||
*
|
*
|
||||||
* @return the optional
|
* @return the to user
|
||||||
*/
|
*/
|
||||||
public Optional<String> password() {
|
public String toUser() {
|
||||||
if (!isDone()) {
|
return toUser;
|
||||||
throw new IllegalStateException("Event is not done.");
|
|
||||||
}
|
|
||||||
return currentResults().stream().findFirst();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2024 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.jgrapes.core.Channel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supports the lookup of a channel by a name (an id). As a convenience,
|
||||||
|
* it is possible to additionally associate arbitrary data with the entry
|
||||||
|
* (and thus with the channel). Note that this interface defines a
|
||||||
|
* read-only view of the dictionary.
|
||||||
|
*
|
||||||
|
* @param <K> the key type
|
||||||
|
* @param <C> the channel type
|
||||||
|
* @param <A> the type of the associated data
|
||||||
|
*/
|
||||||
|
public interface ChannelDictionary<K, C extends Channel, A> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines the channel and the associated data.
|
||||||
|
*
|
||||||
|
* @param <C> the channel type
|
||||||
|
* @param <A> the type of the associated data
|
||||||
|
* @param channel the channel
|
||||||
|
* @param associated the associated
|
||||||
|
*/
|
||||||
|
public record Value<C extends Channel, A>(C channel, A associated) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all known keys.
|
||||||
|
*
|
||||||
|
* @return the keys
|
||||||
|
*/
|
||||||
|
Set<K> keys();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all known values.
|
||||||
|
*
|
||||||
|
* @return the collection
|
||||||
|
*/
|
||||||
|
Collection<Value<C, A>> values();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the channel and associates data registered for the key
|
||||||
|
* or an empty optional if no entry exists.
|
||||||
|
*
|
||||||
|
* @param key the key
|
||||||
|
* @return the result
|
||||||
|
*/
|
||||||
|
Optional<Value<C, A>> value(K key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all known channels.
|
||||||
|
*
|
||||||
|
* @return the collection
|
||||||
|
*/
|
||||||
|
default Collection<C> channels() {
|
||||||
|
return values().stream().map(v -> v.channel).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the channel registered for the key or an empty optional
|
||||||
|
* if no mapping exists.
|
||||||
|
*
|
||||||
|
* @param key the key
|
||||||
|
* @return the optional
|
||||||
|
*/
|
||||||
|
default Optional<C> channel(K key) {
|
||||||
|
return value(key).map(b -> b.channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all known associated data.
|
||||||
|
*
|
||||||
|
* @return the collection
|
||||||
|
*/
|
||||||
|
default Collection<A> associated() {
|
||||||
|
return values().stream()
|
||||||
|
.filter(v -> v.associated() != null)
|
||||||
|
.map(v -> v.associated).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the data associated with the entry for the channel.
|
||||||
|
*
|
||||||
|
* @param key the key
|
||||||
|
* @return the data
|
||||||
|
*/
|
||||||
|
default Optional<A> associated(K key) {
|
||||||
|
return value(key).map(b -> b.associated);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,53 +27,24 @@ import java.util.function.Function;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A channel manager that maintains mappings from a key to a channel.
|
* Provides an actively managed implementation of the {@link ChannelDictionary}.
|
||||||
* As a convenience, it is possible to additionally associate arbitrary
|
|
||||||
* data with the entry (and thus with the channel).
|
|
||||||
*
|
*
|
||||||
* The manager should be used by a component that defines channels for
|
* The {@link ChannelManager} can be used for housekeeping by any component
|
||||||
* housekeeping. It can be shared between this component and another
|
* that creates channels. It can be shared between this component and
|
||||||
* component, preferably using the {@link #fixed()} view for the
|
* some other component, preferably passing it as {@link ChannelDictionary}
|
||||||
* second component. Alternatively, the second component can use a
|
* (the read-only view) to the second component. Alternatively, the other
|
||||||
* {@link ChannelCache} to track the mappings using events.
|
* component can use a {@link ChannelTracker} to track the mappings using
|
||||||
|
* events.
|
||||||
*
|
*
|
||||||
* @param <K> the key type
|
* @param <K> the key type
|
||||||
* @param <C> the channel type
|
* @param <C> the channel type
|
||||||
* @param <A> the type of the associated data
|
* @param <A> the type of the associated data
|
||||||
*/
|
*/
|
||||||
public class ChannelManager<K, C extends Channel, A> {
|
public class ChannelManager<K, C extends Channel, A>
|
||||||
|
implements ChannelDictionary<K, C, A> {
|
||||||
|
|
||||||
private final Map<K, Both<C, A>> channels = new ConcurrentHashMap<>();
|
private final Map<K, Value<C, A>> entries = new ConcurrentHashMap<>();
|
||||||
private final Function<K, C> supplier;
|
private final Function<K, C> supplier;
|
||||||
private ChannelManager<K, C, A> readOnly;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Combines the channel and the associated data.
|
|
||||||
*
|
|
||||||
* @param <C> the generic type
|
|
||||||
* @param <A> the generic type
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("PMD.ShortClassName")
|
|
||||||
public static class Both<C extends Channel, A> {
|
|
||||||
|
|
||||||
/** The channel. */
|
|
||||||
public C channel;
|
|
||||||
|
|
||||||
/** The associated. */
|
|
||||||
public A associated;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates a new both.
|
|
||||||
*
|
|
||||||
* @param channel the channel
|
|
||||||
* @param associated the associated
|
|
||||||
*/
|
|
||||||
public Both(C channel, A associated) {
|
|
||||||
super();
|
|
||||||
this.channel = channel;
|
|
||||||
this.associated = associated;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new channel manager.
|
* Instantiates a new channel manager.
|
||||||
|
|
@ -91,6 +62,26 @@ public class ChannelManager<K, C extends Channel, A> {
|
||||||
this(k -> null);
|
this(k -> null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all keys.
|
||||||
|
*
|
||||||
|
* @return the keys.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Set<K> keys() {
|
||||||
|
return entries.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all known values.
|
||||||
|
*
|
||||||
|
* @return the collection
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Collection<Value<C, A>> values() {
|
||||||
|
return entries.values();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the channel and associates data registered for the key
|
* Returns the channel and associates data registered for the key
|
||||||
* or an empty optional if no mapping exists.
|
* or an empty optional if no mapping exists.
|
||||||
|
|
@ -98,10 +89,8 @@ public class ChannelManager<K, C extends Channel, A> {
|
||||||
* @param key the key
|
* @param key the key
|
||||||
* @return the result
|
* @return the result
|
||||||
*/
|
*/
|
||||||
public Optional<Both<C, A>> both(K key) {
|
public Optional<Value<C, A>> value(K key) {
|
||||||
synchronized (channels) {
|
return Optional.ofNullable(entries.get(key));
|
||||||
return Optional.ofNullable(channels.get(key));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -113,7 +102,7 @@ public class ChannelManager<K, C extends Channel, A> {
|
||||||
* @return the channel manager
|
* @return the channel manager
|
||||||
*/
|
*/
|
||||||
public ChannelManager<K, C, A> put(K key, C channel, A associated) {
|
public ChannelManager<K, C, A> put(K key, C channel, A associated) {
|
||||||
channels.put(key, new Both<>(channel, associated));
|
entries.put(key, new Value<>(channel, associated));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,14 +119,15 @@ public class ChannelManager<K, C extends Channel, A> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the channel registered for the key or an empty optional
|
* Creates a new channel without adding it to the channel manager.
|
||||||
* if no mapping exists.
|
* After fully initializing the channel, it should be added to the
|
||||||
|
* manager using {@link #put(K, C)}.
|
||||||
*
|
*
|
||||||
* @param key the key
|
* @param key the key
|
||||||
* @return the optional
|
* @return the c
|
||||||
*/
|
*/
|
||||||
public Optional<C> channel(K key) {
|
public C createChannel(K key) {
|
||||||
return both(key).map(b -> b.channel);
|
return supplier.apply(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -147,8 +137,8 @@ public class ChannelManager<K, C extends Channel, A> {
|
||||||
* @param key the key
|
* @param key the key
|
||||||
* @return the channel
|
* @return the channel
|
||||||
*/
|
*/
|
||||||
public Optional<C> getChannel(K key) {
|
public C channelGet(K key) {
|
||||||
return getChannel(key, supplier);
|
return computeIfAbsent(key, supplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -159,19 +149,9 @@ public class ChannelManager<K, C extends Channel, A> {
|
||||||
* @param supplier the supplier
|
* @param supplier the supplier
|
||||||
* @return the channel
|
* @return the channel
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.AssignmentInOperand",
|
public C computeIfAbsent(K key, Function<K, C> supplier) {
|
||||||
"PMD.DataflowAnomalyAnalysis" })
|
return entries.computeIfAbsent(key,
|
||||||
public Optional<C> getChannel(K key, Function<K, C> supplier) {
|
k -> new Value<>(supplier.apply(k), null)).channel();
|
||||||
synchronized (channels) {
|
|
||||||
return Optional
|
|
||||||
.of(Optional.ofNullable(channels.get(key))
|
|
||||||
.map(v -> v.channel)
|
|
||||||
.orElseGet(() -> {
|
|
||||||
var channel = supplier.apply(key);
|
|
||||||
channels.put(key, new Both<>(channel, null));
|
|
||||||
return channel;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -183,121 +163,17 @@ public class ChannelManager<K, C extends Channel, A> {
|
||||||
* @return the channel manager
|
* @return the channel manager
|
||||||
*/
|
*/
|
||||||
public ChannelManager<K, C, A> associate(K key, A data) {
|
public ChannelManager<K, C, A> associate(K key, A data) {
|
||||||
synchronized (channels) {
|
Optional.ofNullable(entries.computeIfPresent(key,
|
||||||
Optional.ofNullable(channels.get(key))
|
(k, existing) -> new Value<>(existing.channel(), data)));
|
||||||
.ifPresent(v -> v.associated = data);
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the data associated with the entry for the channel.
|
|
||||||
*
|
|
||||||
* @param key the key
|
|
||||||
* @return the data
|
|
||||||
*/
|
|
||||||
public Optional<A> associated(K key) {
|
|
||||||
return both(key).map(b -> b.associated);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all associated data.
|
|
||||||
*
|
|
||||||
* @return the collection
|
|
||||||
*/
|
|
||||||
public Collection<A> associated() {
|
|
||||||
synchronized (channels) {
|
|
||||||
return channels.values().stream()
|
|
||||||
.filter(v -> v.associated != null)
|
|
||||||
.map(v -> v.associated).toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the channel with the given name.
|
* Removes the channel with the given name.
|
||||||
*
|
*
|
||||||
* @param name the name
|
* @param name the name
|
||||||
*/
|
*/
|
||||||
public void remove(String name) {
|
public void remove(String name) {
|
||||||
synchronized (channels) {
|
entries.remove(name);
|
||||||
channels.remove(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all known keys.
|
|
||||||
*
|
|
||||||
* @return the sets the
|
|
||||||
*/
|
|
||||||
public Set<K> keys() {
|
|
||||||
return channels.keySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a read only view of this channel manager. The methods
|
|
||||||
* that usually create a new entry refrain from doing so. The
|
|
||||||
* methods that change the value of channel and {@link #remove(String)}
|
|
||||||
* do nothing. The associated data, however, can still be changed.
|
|
||||||
*
|
|
||||||
* @return the channel manager
|
|
||||||
*/
|
|
||||||
public ChannelManager<K, C, A> fixed() {
|
|
||||||
if (readOnly == null) {
|
|
||||||
readOnly = new ChannelManager<>(supplier) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Both<C, A>> both(K key) {
|
|
||||||
return ChannelManager.this.both(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelManager<K, C, A> put(K key, C channel,
|
|
||||||
A associated) {
|
|
||||||
return associate(key, associated);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<C> getChannel(K key) {
|
|
||||||
return ChannelManager.this.channel(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<C> getChannel(K key, Function<K, C> supplier) {
|
|
||||||
return ChannelManager.this.channel(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelManager<K, C, A> associate(K key, A data) {
|
|
||||||
return ChannelManager.this.associate(key, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<A> associated(K key) {
|
|
||||||
return ChannelManager.this.associated(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<A> associated() {
|
|
||||||
return ChannelManager.this.associated();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove(String name) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<K> keys() {
|
|
||||||
return ChannelManager.this.keys();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelManager<K, C, A> fixed() {
|
|
||||||
return ChannelManager.this.fixed();
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return readOnly;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
package org.jdrupes.vmoperator.manager.events;
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
@ -27,20 +28,30 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A channel manager that tracks mappings from a key to a channel using
|
* Used to track mapping from a key to a channel. Entries must
|
||||||
* "add/remove" (or "open/close") events and the channels on which they
|
* be maintained by handlers for "add/remove" (or "open/close")
|
||||||
* are delivered.
|
* events delivered on the channels that are to be
|
||||||
|
* made available by the tracker.
|
||||||
|
*
|
||||||
|
* The channels are stored in the dictionary using {@link WeakReference}s.
|
||||||
|
* Removing entries is therefore best practice but not an absolute necessity
|
||||||
|
* as entries for cleared references are removed when one of the methods
|
||||||
|
* {@link #values()}, {@link #channels()} or {@link #associated()} is called.
|
||||||
*
|
*
|
||||||
* @param <K> the key type
|
* @param <K> the key type
|
||||||
* @param <C> the channel type
|
* @param <C> the channel type
|
||||||
* @param <A> the type of the associated data
|
* @param <A> the type of the associated data
|
||||||
*/
|
*/
|
||||||
public class ChannelCache<K, C extends Channel, A> {
|
public class ChannelTracker<K, C extends Channel, A>
|
||||||
|
implements ChannelDictionary<K, C, A> {
|
||||||
|
|
||||||
private final Map<K, Data<C, A>> channels = new ConcurrentHashMap<>();
|
private final Map<K, Data<C, A>> entries = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper
|
* Combines the channel and associated data.
|
||||||
|
*
|
||||||
|
* @param <C> the generic type
|
||||||
|
* @param <A> the generic type
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.ShortClassName")
|
@SuppressWarnings("PMD.ShortClassName")
|
||||||
private static class Data<C extends Channel, A> {
|
private static class Data<C extends Channel, A> {
|
||||||
|
|
@ -57,32 +68,24 @@ public class ChannelCache<K, C extends Channel, A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Combines the channel and the associated data.
|
public Set<K> keys() {
|
||||||
*
|
return entries.keySet();
|
||||||
* @param <C> the generic type
|
}
|
||||||
* @param <A> the generic type
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("PMD.ShortClassName")
|
|
||||||
public static class Both<C extends Channel, A> {
|
|
||||||
|
|
||||||
/** The channel. */
|
@Override
|
||||||
public C channel;
|
public Collection<Value<C, A>> values() {
|
||||||
|
var result = new ArrayList<Value<C, A>>();
|
||||||
/** The associated. */
|
for (var itr = entries.entrySet().iterator(); itr.hasNext();) {
|
||||||
public A associated;
|
var value = itr.next().getValue();
|
||||||
|
var channel = value.channel.get();
|
||||||
/**
|
if (channel == null) {
|
||||||
* Instantiates a new both.
|
itr.remove();
|
||||||
*
|
continue;
|
||||||
* @param channel the channel
|
}
|
||||||
* @param associated the associated
|
result.add(new Value<>(channel, value.associated));
|
||||||
*/
|
|
||||||
public Both(C channel, A associated) {
|
|
||||||
super();
|
|
||||||
this.channel = channel;
|
|
||||||
this.associated = associated;
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -92,20 +95,18 @@ public class ChannelCache<K, C extends Channel, A> {
|
||||||
* @param key the key
|
* @param key the key
|
||||||
* @return the result
|
* @return the result
|
||||||
*/
|
*/
|
||||||
public Optional<Both<C, A>> both(K key) {
|
public Optional<Value<C, A>> value(K key) {
|
||||||
synchronized (channels) {
|
var value = entries.get(key);
|
||||||
var value = channels.get(key);
|
if (value == null) {
|
||||||
if (value == null) {
|
return Optional.empty();
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
var channel = value.channel.get();
|
|
||||||
if (channel == null) {
|
|
||||||
// Cleanup old reference
|
|
||||||
channels.remove(key);
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
return Optional.of(new Both<>(channel, value.associated));
|
|
||||||
}
|
}
|
||||||
|
var channel = value.channel.get();
|
||||||
|
if (channel == null) {
|
||||||
|
// Cleanup old reference
|
||||||
|
entries.remove(key);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(new Value<>(channel, value.associated));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -116,10 +117,10 @@ public class ChannelCache<K, C extends Channel, A> {
|
||||||
* @param associated the associated
|
* @param associated the associated
|
||||||
* @return the channel manager
|
* @return the channel manager
|
||||||
*/
|
*/
|
||||||
public ChannelCache<K, C, A> put(K key, C channel, A associated) {
|
public ChannelTracker<K, C, A> put(K key, C channel, A associated) {
|
||||||
Data<C, A> data = new Data<>(channel);
|
Data<C, A> data = new Data<>(channel);
|
||||||
data.associated = associated;
|
data.associated = associated;
|
||||||
channels.put(key, data);
|
entries.put(key, data);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,22 +131,11 @@ public class ChannelCache<K, C extends Channel, A> {
|
||||||
* @param channel the channel
|
* @param channel the channel
|
||||||
* @return the channel manager
|
* @return the channel manager
|
||||||
*/
|
*/
|
||||||
public ChannelCache<K, C, A> put(K key, C channel) {
|
public ChannelTracker<K, C, A> put(K key, C channel) {
|
||||||
put(key, channel, null);
|
put(key, channel, null);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the channel registered for the key or an empty optional
|
|
||||||
* if no mapping exists.
|
|
||||||
*
|
|
||||||
* @param key the key
|
|
||||||
* @return the optional
|
|
||||||
*/
|
|
||||||
public Optional<C> channel(K key) {
|
|
||||||
return both(key).map(b -> b.channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Associate the entry for the channel with the given data. The entry
|
* Associate the entry for the channel with the given data. The entry
|
||||||
* for the channel must already exist.
|
* for the channel must already exist.
|
||||||
|
|
@ -154,54 +144,18 @@ public class ChannelCache<K, C extends Channel, A> {
|
||||||
* @param data the data
|
* @param data the data
|
||||||
* @return the channel manager
|
* @return the channel manager
|
||||||
*/
|
*/
|
||||||
public ChannelCache<K, C, A> associate(K key, A data) {
|
public ChannelTracker<K, C, A> associate(K key, A data) {
|
||||||
synchronized (channels) {
|
Optional.ofNullable(entries.get(key))
|
||||||
Optional.ofNullable(channels.get(key))
|
.ifPresent(v -> v.associated = data);
|
||||||
.ifPresent(v -> v.associated = data);
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the data associated with the entry for the channel.
|
|
||||||
*
|
|
||||||
* @param key the key
|
|
||||||
* @return the data
|
|
||||||
*/
|
|
||||||
public Optional<A> associated(K key) {
|
|
||||||
return both(key).map(b -> b.associated);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all associated data.
|
|
||||||
*
|
|
||||||
* @return the collection
|
|
||||||
*/
|
|
||||||
public Collection<A> associated() {
|
|
||||||
synchronized (channels) {
|
|
||||||
return channels.values().stream()
|
|
||||||
.filter(v -> v.channel.get() != null && v.associated != null)
|
|
||||||
.map(v -> v.associated).toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the channel with the given name.
|
* Removes the channel with the given name.
|
||||||
*
|
*
|
||||||
* @param name the name
|
* @param name the name
|
||||||
*/
|
*/
|
||||||
public void remove(String name) {
|
public void remove(String name) {
|
||||||
synchronized (channels) {
|
entries.remove(name);
|
||||||
channels.remove(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all known keys.
|
|
||||||
*
|
|
||||||
* @return the sets the
|
|
||||||
*/
|
|
||||||
public Set<K> keys() {
|
|
||||||
return channels.keySet();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2024 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
|
import org.jdrupes.vmoperator.common.VmDefinition;
|
||||||
|
import org.jgrapes.core.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current display secret and optionally updates it.
|
||||||
|
*/
|
||||||
|
public class GetDisplaySecret extends Event<String> {
|
||||||
|
|
||||||
|
private final VmDefinition vmDef;
|
||||||
|
private final String user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new request for the display secret.
|
||||||
|
* After handling the event, a result of `null` means that
|
||||||
|
* no secret is needed. No result means that the console
|
||||||
|
* is not accessible.
|
||||||
|
*
|
||||||
|
* @param vmDef the vm name
|
||||||
|
* @param user the requesting user
|
||||||
|
*/
|
||||||
|
public GetDisplaySecret(VmDefinition vmDef, String user) {
|
||||||
|
this.vmDef = vmDef;
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the VM definition.
|
||||||
|
*
|
||||||
|
* @return the VM definition
|
||||||
|
*/
|
||||||
|
public VmDefinition vmDefinition() {
|
||||||
|
return vmDef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the id of the user who has requested the password.
|
||||||
|
*
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
public String user() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` if a password is available. May only be called
|
||||||
|
* when the event is completed. Note that the password returned
|
||||||
|
* by {@link #secret()} may be `null`, indicating that no password
|
||||||
|
* is needed.
|
||||||
|
*
|
||||||
|
* @return true, if successful
|
||||||
|
*/
|
||||||
|
public boolean secretAvailable() {
|
||||||
|
if (!isDone()) {
|
||||||
|
throw new IllegalStateException("Event is not done.");
|
||||||
|
}
|
||||||
|
return !currentResults().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the secret. May only be called when the event has been
|
||||||
|
* completed with a valid result (see {@link #secretAvailable()}).
|
||||||
|
*
|
||||||
|
* @return the password. A value of `null` means that no password
|
||||||
|
* is required.
|
||||||
|
*/
|
||||||
|
public String secret() {
|
||||||
|
if (!isDone() || currentResults().isEmpty()) {
|
||||||
|
throw new IllegalStateException("Event is not done.");
|
||||||
|
}
|
||||||
|
return currentResults().get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2024 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.jdrupes.vmoperator.common.VmPool;
|
||||||
|
import org.jgrapes.core.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the known pools' definitions.
|
||||||
|
*/
|
||||||
|
public class GetPools extends Event<List<VmPool>> {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String user;
|
||||||
|
private List<String> roles = Collections.emptyList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return only the pool with the given name.
|
||||||
|
*
|
||||||
|
* @param name the name
|
||||||
|
* @return the returns the vms
|
||||||
|
*/
|
||||||
|
public GetPools withName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return only {@link VmPool}s that are accessible by
|
||||||
|
* the given user or roles.
|
||||||
|
*
|
||||||
|
* @param user the user
|
||||||
|
* @param roles the roles
|
||||||
|
* @return the event
|
||||||
|
*/
|
||||||
|
public GetPools accessibleFor(String user, List<String> roles) {
|
||||||
|
this.user = user;
|
||||||
|
this.roles = roles;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name filter criterion, if set.
|
||||||
|
*
|
||||||
|
* @return the optional
|
||||||
|
*/
|
||||||
|
public Optional<String> name() {
|
||||||
|
return Optional.ofNullable(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user filter criterion, if set.
|
||||||
|
*
|
||||||
|
* @return the optional
|
||||||
|
*/
|
||||||
|
public Optional<String> forUser() {
|
||||||
|
return Optional.ofNullable(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the roles criterion.
|
||||||
|
*
|
||||||
|
* @return the list
|
||||||
|
*/
|
||||||
|
public List<String> forRoles() {
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2024 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.jdrupes.vmoperator.common.VmDefinition;
|
||||||
|
import org.jgrapes.core.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the known VMs' definitions and channels.
|
||||||
|
*/
|
||||||
|
public class GetVms extends Event<List<GetVms.VmData>> {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String user;
|
||||||
|
private List<String> roles = Collections.emptyList();
|
||||||
|
private String fromPool;
|
||||||
|
private String toUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return only the VMs with the given name.
|
||||||
|
*
|
||||||
|
* @param name the name
|
||||||
|
* @return the returns the vms
|
||||||
|
*/
|
||||||
|
public GetVms withName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return only {@link VmDefinition}s that are accessible by
|
||||||
|
* the given user or roles.
|
||||||
|
*
|
||||||
|
* @param user the user
|
||||||
|
* @param roles the roles
|
||||||
|
* @return the event
|
||||||
|
*/
|
||||||
|
public GetVms accessibleFor(String user, List<String> roles) {
|
||||||
|
this.user = user;
|
||||||
|
this.roles = roles;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return only {@link VmDefinition}s that are assigned from the given pool.
|
||||||
|
*
|
||||||
|
* @param pool the pool
|
||||||
|
* @return the returns the vms
|
||||||
|
*/
|
||||||
|
public GetVms assignedFrom(String pool) {
|
||||||
|
this.fromPool = pool;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return only {@link VmDefinition}s that are assigned to the given user.
|
||||||
|
*
|
||||||
|
* @param user the user
|
||||||
|
* @return the returns the vms
|
||||||
|
*/
|
||||||
|
public GetVms assignedTo(String user) {
|
||||||
|
this.toUser = user;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name filter criterion, if set.
|
||||||
|
*
|
||||||
|
* @return the optional
|
||||||
|
*/
|
||||||
|
public Optional<String> name() {
|
||||||
|
return Optional.ofNullable(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user filter criterion, if set.
|
||||||
|
*
|
||||||
|
* @return the optional
|
||||||
|
*/
|
||||||
|
public Optional<String> user() {
|
||||||
|
return Optional.ofNullable(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the roles criterion.
|
||||||
|
*
|
||||||
|
* @return the list
|
||||||
|
*/
|
||||||
|
public List<String> roles() {
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the pool filter criterion, if set.
|
||||||
|
*
|
||||||
|
* @return the optional
|
||||||
|
*/
|
||||||
|
public Optional<String> fromPool() {
|
||||||
|
return Optional.ofNullable(fromPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the user filter criterion, if set.
|
||||||
|
*
|
||||||
|
* @return the optional
|
||||||
|
*/
|
||||||
|
public Optional<String> toUser() {
|
||||||
|
return Optional.ofNullable(toUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return tuple.
|
||||||
|
*
|
||||||
|
* @param definition the definition
|
||||||
|
* @param channel the channel
|
||||||
|
*/
|
||||||
|
public record VmData(VmDefinition definition, VmChannel channel) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,7 +24,6 @@ import org.jgrapes.core.Event;
|
||||||
/**
|
/**
|
||||||
* Modifies a VM.
|
* Modifies a VM.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataClass")
|
|
||||||
public class ModifyVm extends Event<Void> {
|
public class ModifyVm extends Event<Void> {
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* VM-Operator
|
* VM-Operator
|
||||||
* Copyright (C) 2024 Michael N. Lipp
|
* Copyright (C) 2023 Michael N. Lipp
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
|
@ -18,30 +18,38 @@
|
||||||
|
|
||||||
package org.jdrupes.vmoperator.manager.events;
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
import io.kubernetes.client.openapi.models.V1Service;
|
import io.kubernetes.client.openapi.models.V1Pod;
|
||||||
import org.jdrupes.vmoperator.common.K8sObserver.ResponseType;
|
import org.jdrupes.vmoperator.common.K8sObserver;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
import org.jgrapes.core.Components;
|
import org.jgrapes.core.Components;
|
||||||
import org.jgrapes.core.Event;
|
import org.jgrapes.core.Event;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that a service has changed.
|
* Indicates a change in a pod that runs a VM.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataClass")
|
public class PodChanged extends Event<Void> {
|
||||||
public class ServiceChanged extends Event<Void> {
|
|
||||||
|
|
||||||
private final ResponseType type;
|
private final V1Pod pod;
|
||||||
private final V1Service service;
|
private final K8sObserver.ResponseType type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new service changed event.
|
* Instantiates a new VM changed event.
|
||||||
*
|
*
|
||||||
|
* @param pod the pod
|
||||||
* @param type the type
|
* @param type the type
|
||||||
* @param service the service
|
|
||||||
*/
|
*/
|
||||||
public ServiceChanged(ResponseType type, V1Service service) {
|
public PodChanged(V1Pod pod, K8sObserver.ResponseType type) {
|
||||||
|
this.pod = pod;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.service = service;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the pod.
|
||||||
|
*
|
||||||
|
* @return the pod
|
||||||
|
*/
|
||||||
|
public V1Pod pod() {
|
||||||
|
return pod;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -49,24 +57,15 @@ public class ServiceChanged extends Event<Void> {
|
||||||
*
|
*
|
||||||
* @return the type
|
* @return the type
|
||||||
*/
|
*/
|
||||||
public ResponseType type() {
|
public K8sObserver.ResponseType type() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the service.
|
|
||||||
*
|
|
||||||
* @return the service
|
|
||||||
*/
|
|
||||||
public V1Service service() {
|
|
||||||
return service;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
builder.append(Components.objectName(this)).append(" [")
|
builder.append(Components.objectName(this)).append(" [")
|
||||||
.append(service.getMetadata().getName()).append(' ').append(type);
|
.append(pod.getMetadata().getName()).append(' ').append(type);
|
||||||
if (channels() != null) {
|
if (channels() != null) {
|
||||||
builder.append(", channels=").append(Channel.toString(channels()));
|
builder.append(", channels=").append(Channel.toString(channels()));
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2024 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
|
import org.jgrapes.core.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a reset of the VM.
|
||||||
|
*/
|
||||||
|
public class ResetVm extends Event<String> {
|
||||||
|
|
||||||
|
private final String vmName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new event.
|
||||||
|
*
|
||||||
|
* @param vmName the vm name
|
||||||
|
*/
|
||||||
|
public ResetVm(String vmName) {
|
||||||
|
this.vmName = vmName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the vm name.
|
||||||
|
*
|
||||||
|
* @return the vm name
|
||||||
|
*/
|
||||||
|
public String vmName() {
|
||||||
|
return vmName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2024 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
|
import org.jdrupes.vmoperator.common.VmPool;
|
||||||
|
import org.jgrapes.core.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note the assignment to a user in the VM status.
|
||||||
|
*/
|
||||||
|
public class UpdateAssignment extends Event<Boolean> {
|
||||||
|
|
||||||
|
private final VmPool fromPool;
|
||||||
|
private final String toUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new event.
|
||||||
|
*
|
||||||
|
* @param fromPool the pool from which the VM was assigned
|
||||||
|
* @param toUser the to user
|
||||||
|
*/
|
||||||
|
public UpdateAssignment(VmPool fromPool, String toUser) {
|
||||||
|
this.fromPool = fromPool;
|
||||||
|
this.toUser = toUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the pool from which the VM was assigned.
|
||||||
|
*
|
||||||
|
* @return the pool
|
||||||
|
*/
|
||||||
|
public VmPool fromPool() {
|
||||||
|
return fromPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the user to whom the VM was assigned.
|
||||||
|
*
|
||||||
|
* @return the to user
|
||||||
|
*/
|
||||||
|
public String toUser() {
|
||||||
|
return toUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,20 +19,20 @@
|
||||||
package org.jdrupes.vmoperator.manager.events;
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
import org.jdrupes.vmoperator.common.K8sClient;
|
import org.jdrupes.vmoperator.common.K8sClient;
|
||||||
import org.jdrupes.vmoperator.common.VmDefinitionModel;
|
import org.jdrupes.vmoperator.common.VmDefinition;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
|
import org.jgrapes.core.Event;
|
||||||
import org.jgrapes.core.EventPipeline;
|
import org.jgrapes.core.EventPipeline;
|
||||||
import org.jgrapes.core.Subchannel.DefaultSubchannel;
|
import org.jgrapes.core.Subchannel.DefaultSubchannel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A subchannel used to send the events related to a specific VM.
|
* A subchannel used to send the events related to a specific VM.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataClass")
|
|
||||||
public class VmChannel extends DefaultSubchannel {
|
public class VmChannel extends DefaultSubchannel {
|
||||||
|
|
||||||
private final EventPipeline pipeline;
|
private final EventPipeline pipeline;
|
||||||
private final K8sClient client;
|
private final K8sClient client;
|
||||||
private VmDefinitionModel vmDefinition;
|
private VmDefinition definition;
|
||||||
private long generation = -1;
|
private long generation = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -55,19 +55,18 @@ public class VmChannel extends DefaultSubchannel {
|
||||||
* @param definition the definition
|
* @param definition the definition
|
||||||
* @return the watch channel
|
* @return the watch channel
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.LinguisticNaming")
|
public VmChannel setVmDefinition(VmDefinition definition) {
|
||||||
public VmChannel setVmDefinition(VmDefinitionModel definition) {
|
this.definition = definition;
|
||||||
this.vmDefinition = definition;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the last known definition of the VM.
|
* Returns the last known definition of the VM.
|
||||||
*
|
*
|
||||||
* @return the json object
|
* @return the defintion
|
||||||
*/
|
*/
|
||||||
public VmDefinitionModel vmDefinition() {
|
public VmDefinition vmDefinition() {
|
||||||
return vmDefinition;
|
return definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -86,7 +85,6 @@ public class VmChannel extends DefaultSubchannel {
|
||||||
* @param generation the generation to set
|
* @param generation the generation to set
|
||||||
* @return true if value has changed
|
* @return true if value has changed
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.LinguisticNaming")
|
|
||||||
public boolean setGeneration(long generation) {
|
public boolean setGeneration(long generation) {
|
||||||
if (this.generation == generation) {
|
if (this.generation == generation) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -104,6 +102,19 @@ public class VmChannel extends DefaultSubchannel {
|
||||||
return pipeline;
|
return pipeline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire the given event on this channel, using the associated
|
||||||
|
* {@link #pipeline()}.
|
||||||
|
*
|
||||||
|
* @param <T> the generic type
|
||||||
|
* @param event the event
|
||||||
|
* @return the t
|
||||||
|
*/
|
||||||
|
public <T extends Event<?>> T fire(T event) {
|
||||||
|
pipeline.fire(event, this);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the API client.
|
* Returns the API client.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* VM-Operator
|
||||||
|
* Copyright (C) 2023 Michael N. Lipp
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
|
import org.jdrupes.vmoperator.common.VmPool;
|
||||||
|
import org.jgrapes.core.Channel;
|
||||||
|
import org.jgrapes.core.Components;
|
||||||
|
import org.jgrapes.core.Event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates a change in a pool configuration.
|
||||||
|
*/
|
||||||
|
public class VmPoolChanged extends Event<Void> {
|
||||||
|
|
||||||
|
private final VmPool vmPool;
|
||||||
|
private final boolean deleted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new VM changed event.
|
||||||
|
*
|
||||||
|
* @param pool the pool
|
||||||
|
* @param deleted true, if the pool was deleted
|
||||||
|
*/
|
||||||
|
public VmPoolChanged(VmPool pool, boolean deleted) {
|
||||||
|
vmPool = pool;
|
||||||
|
this.deleted = deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new VM changed event for an existing pool.
|
||||||
|
*
|
||||||
|
* @param pool the pool
|
||||||
|
*/
|
||||||
|
public VmPoolChanged(VmPool pool) {
|
||||||
|
this(pool, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the VM pool.
|
||||||
|
*
|
||||||
|
* @return the vm pool
|
||||||
|
*/
|
||||||
|
public VmPool vmPool() {
|
||||||
|
return vmPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pool has been deleted.
|
||||||
|
*
|
||||||
|
* @return true, if successful
|
||||||
|
*/
|
||||||
|
public boolean deleted() {
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder builder = new StringBuilder(30);
|
||||||
|
builder.append(Components.objectName(this))
|
||||||
|
.append(" [");
|
||||||
|
if (deleted) {
|
||||||
|
builder.append("Deleted: ");
|
||||||
|
}
|
||||||
|
builder.append(vmPool);
|
||||||
|
if (channels() != null) {
|
||||||
|
builder.append(", channels=").append(Channel.toString(channels()));
|
||||||
|
}
|
||||||
|
builder.append(']');
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,37 +19,41 @@
|
||||||
package org.jdrupes.vmoperator.manager.events;
|
package org.jdrupes.vmoperator.manager.events;
|
||||||
|
|
||||||
import org.jdrupes.vmoperator.common.K8sObserver;
|
import org.jdrupes.vmoperator.common.K8sObserver;
|
||||||
import org.jdrupes.vmoperator.common.VmDefinitionModel;
|
import org.jdrupes.vmoperator.common.VmDefinition;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
import org.jgrapes.core.Components;
|
import org.jgrapes.core.Components;
|
||||||
import org.jgrapes.core.Event;
|
import org.jgrapes.core.Event;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates a change in a VM definition. Note that the definition
|
* Indicates a change in a VM "resource". Note that the resource
|
||||||
* consists of the metadata (mostly immutable), the "spec" and the
|
* combines the VM CR's metadata (mostly immutable), the VM CR's
|
||||||
* "status" parts. Consumers that are only interested in "spec"
|
* "spec" part, the VM CR's "status" subresource and state information
|
||||||
* changes should check {@link #specChanged()} before processing
|
* from the pod. Consumers that are only interested in "spec" changes
|
||||||
* the event any further.
|
* should check {@link #specChanged()} before processing the event any
|
||||||
|
* further.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataClass")
|
@SuppressWarnings("PMD.DataClass")
|
||||||
public class VmDefChanged extends Event<Void> {
|
public class VmResourceChanged extends Event<Void> {
|
||||||
|
|
||||||
private final K8sObserver.ResponseType type;
|
private final K8sObserver.ResponseType type;
|
||||||
|
private final VmDefinition vmDefinition;
|
||||||
private final boolean specChanged;
|
private final boolean specChanged;
|
||||||
private final VmDefinitionModel vmDef;
|
private final boolean podChanged;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new VM changed event.
|
* Instantiates a new VM changed event.
|
||||||
*
|
*
|
||||||
* @param type the type
|
* @param type the type
|
||||||
* @param specChanged the spec part changed
|
|
||||||
* @param vmDefinition the VM definition
|
* @param vmDefinition the VM definition
|
||||||
|
* @param specChanged the spec part changed
|
||||||
*/
|
*/
|
||||||
public VmDefChanged(K8sObserver.ResponseType type, boolean specChanged,
|
public VmResourceChanged(K8sObserver.ResponseType type,
|
||||||
VmDefinitionModel vmDefinition) {
|
VmDefinition vmDefinition, boolean specChanged,
|
||||||
|
boolean podChanged) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
this.vmDefinition = vmDefinition;
|
||||||
this.specChanged = specChanged;
|
this.specChanged = specChanged;
|
||||||
this.vmDef = vmDefinition;
|
this.podChanged = podChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -61,6 +65,15 @@ public class VmDefChanged extends Event<Void> {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the VM definition.
|
||||||
|
*
|
||||||
|
* @return the VM definition
|
||||||
|
*/
|
||||||
|
public VmDefinition vmDefinition() {
|
||||||
|
return vmDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the "spec" part changed.
|
* Indicates if the "spec" part changed.
|
||||||
*/
|
*/
|
||||||
|
|
@ -69,19 +82,17 @@ public class VmDefChanged extends Event<Void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the object.
|
* Indicates if the pod status changed.
|
||||||
*
|
|
||||||
* @return the object.
|
|
||||||
*/
|
*/
|
||||||
public VmDefinitionModel vmDefinition() {
|
public boolean podChanged() {
|
||||||
return vmDef;
|
return podChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
builder.append(Components.objectName(this)).append(" [")
|
builder.append(Components.objectName(this)).append(" [")
|
||||||
.append(vmDef.getMetadata().getName()).append(' ').append(type);
|
.append(vmDefinition.name()).append(' ').append(type);
|
||||||
if (channels() != null) {
|
if (channels() != null) {
|
||||||
builder.append(", channels=").append(Channel.toString(channels()));
|
builder.append(", channels=").append(Channel.toString(channels()));
|
||||||
}
|
}
|
||||||
1
org.jdrupes.vmoperator.manager/.gitignore
vendored
Normal file
1
org.jdrupes.vmoperator.manager/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/logging.properties
|
||||||
|
|
@ -13,15 +13,14 @@ dependencies {
|
||||||
|
|
||||||
implementation 'commons-cli:commons-cli:1.5.0'
|
implementation 'commons-cli:commons-cli:1.5.0'
|
||||||
|
|
||||||
implementation 'org.jgrapes:org.jgrapes.core:[1.19.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.util:[1.38.1,2)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.io:[2.7.0,3)'
|
implementation 'org.jgrapes:org.jgrapes.io:[2.12.1,3)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.http:[3.1.0,4)'
|
implementation 'org.jgrapes:org.jgrapes.http:[3.5.0,4)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.util:[1.34.0,2)'
|
|
||||||
|
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconsole.base:[1.5.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconsole.base:[2.3.0,3)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.5.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconsole.vuejs:[1.8.0,2)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.3.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconsole.rbac:[1.4.0,2)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconlet.oidclogin:[1.3.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconlet.oidclogin:[1.7.0,2)'
|
||||||
implementation 'org.jgrapes:org.jgrapes.webconlet.markdowndisplay:[1.2.0,2)'
|
implementation 'org.jgrapes:org.jgrapes.webconlet.markdowndisplay:[1.2.0,2)'
|
||||||
|
|
||||||
runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.sysinfo:[1.4.0,2)'
|
runtimeOnly 'org.jgrapes:org.jgrapes.webconlet.sysinfo:[1.4.0,2)'
|
||||||
|
|
@ -32,8 +31,8 @@ dependencies {
|
||||||
runtimeOnly 'org.slf4j:slf4j-jdk14:[2.0.7,3)'
|
runtimeOnly 'org.slf4j:slf4j-jdk14:[2.0.7,3)'
|
||||||
runtimeOnly 'org.apache.logging.log4j:log4j-to-jul:2.20.0'
|
runtimeOnly 'org.apache.logging.log4j:log4j-to-jul:2.20.0'
|
||||||
|
|
||||||
runtimeOnly project(':org.jdrupes.vmoperator.vmconlet')
|
runtimeOnly project(':org.jdrupes.vmoperator.vmmgmt')
|
||||||
runtimeOnly project(':org.jdrupes.vmoperator.vmviewer')
|
runtimeOnly project(':org.jdrupes.vmoperator.vmaccess')
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
|
|
@ -45,75 +44,69 @@ application {
|
||||||
mainClass = 'org.jdrupes.vmoperator.manager.Manager'
|
mainClass = 'org.jdrupes.vmoperator.manager.Manager'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
project.ext.gitBranch = grgit.branch.current.name.replace('/', '-')
|
||||||
|
def registry = "${project.rootProject.properties['docker.registry']}"
|
||||||
|
def rootVersion = rootProject.version
|
||||||
|
|
||||||
task buildImage(type: Exec) {
|
task buildImage(type: Exec) {
|
||||||
dependsOn installDist
|
dependsOn installDist
|
||||||
inputs.files 'src/org/jdrupes/vmoperator/manager/Containerfile'
|
inputs.files 'src/org/jdrupes/vmoperator/manager/Containerfile'
|
||||||
|
|
||||||
commandLine 'podman', 'build', '--pull',
|
commandLine 'podman', 'build', '--pull',
|
||||||
'-t', "${project.name}:${project.version}",\
|
'-t', "${project.name}:${project.gitBranch}",\
|
||||||
'-f', 'src/org/jdrupes/vmoperator/manager/Containerfile', '.'
|
'-f', 'src/org/jdrupes/vmoperator/manager/Containerfile', '.'
|
||||||
}
|
}
|
||||||
|
|
||||||
task tagLatestImage(type: Exec) {
|
|
||||||
dependsOn buildImage
|
|
||||||
|
|
||||||
enabled = !project.version.contains("SNAPSHOT")
|
|
||||||
&& !project.version.contains("alpha") \
|
|
||||||
&& !project.version.contains("beta") \
|
|
||||||
|| project.rootProject.properties['docker.testRegistry'] \
|
|
||||||
&& project.rootProject.properties['docker.registry'] \
|
|
||||||
== project.rootProject.properties['docker.testRegistry']
|
|
||||||
|
|
||||||
commandLine 'podman', 'tag', "${project.name}:${project.version}",\
|
|
||||||
"${project.name}:latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
task buildLatestImage {
|
|
||||||
dependsOn buildImage
|
|
||||||
dependsOn tagLatestImage
|
|
||||||
}
|
|
||||||
|
|
||||||
task pushImage(type: Exec) {
|
task pushImage(type: Exec) {
|
||||||
dependsOn buildImage
|
dependsOn buildImage
|
||||||
|
// Don't push without testing first
|
||||||
|
dependsOn test
|
||||||
|
|
||||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
commandLine 'podman', 'push', '--tls-verify=false', \
|
||||||
"localhost/${project.name}:${project.version}", \
|
"${project.name}:${project.gitBranch}", \
|
||||||
"${project.rootProject.properties['docker.registry']}" \
|
"${registry}/${project.name}:${project.gitBranch}"
|
||||||
+ "/${project.name}:${project.version}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task pushLatestImage(type: Exec) {
|
task tagWithVersion(type: Exec) {
|
||||||
dependsOn buildLatestImage
|
dependsOn pushImage
|
||||||
|
|
||||||
enabled = !project.version.contains("SNAPSHOT")
|
enabled = !rootVersion.contains("SNAPSHOT")
|
||||||
&& !project.version.contains("alpha") \
|
|
||||||
&& !project.version.contains("beta") \
|
commandLine 'podman', 'push', \
|
||||||
|
"${project.name}:${project.gitBranch}",\
|
||||||
|
"${registry}/${project.name}:${project.version}"
|
||||||
|
}
|
||||||
|
|
||||||
|
task tagAsLatest(type: Exec) {
|
||||||
|
dependsOn tagWithVersion
|
||||||
|
|
||||||
|
enabled = !rootVersion.contains("SNAPSHOT")
|
||||||
|
&& !rootVersion.contains("alpha") \
|
||||||
|
&& !rootVersion.contains("beta") \
|
||||||
|| project.rootProject.properties['docker.testRegistry'] \
|
|| project.rootProject.properties['docker.testRegistry'] \
|
||||||
&& project.rootProject.properties['docker.registry'] \
|
&& project.rootProject.properties['docker.registry'] \
|
||||||
== project.rootProject.properties['docker.testRegistry']
|
== project.rootProject.properties['docker.testRegistry']
|
||||||
|
|
||||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
commandLine 'podman', 'push', \
|
||||||
"localhost/${project.name}:${project.version}", \
|
"${project.name}:${project.gitBranch}",\
|
||||||
"${project.rootProject.properties['docker.registry']}" \
|
"${registry}/${project.name}:latest"
|
||||||
+ "/${project.name}:latest"
|
}
|
||||||
|
|
||||||
|
task publishImage {
|
||||||
|
dependsOn pushImage
|
||||||
|
dependsOn tagWithVersion
|
||||||
|
dependsOn tagAsLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
task pushForTest(type: Exec) {
|
task pushForTest(type: Exec) {
|
||||||
dependsOn buildImage
|
dependsOn buildImage
|
||||||
|
|
||||||
commandLine 'podman', 'push', '--tls-verify=false', \
|
commandLine 'podman', 'push', '--tls-verify=false', \
|
||||||
"localhost/${project.name}:${project.version}", \
|
"${project.name}:${project.gitBranch}", \
|
||||||
"${project.rootProject.properties['docker.registry']}" \
|
"${project.rootProject.properties['docker.testRegistry']}" \
|
||||||
+ "/${project.name}:test"
|
+ "/${project.name}:test"
|
||||||
}
|
}
|
||||||
|
|
||||||
task pushImages {
|
|
||||||
// Don't push without testing first
|
|
||||||
dependsOn test
|
|
||||||
dependsOn pushImage
|
|
||||||
dependsOn pushLatestImage
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
test {
|
||||||
enabled = project.hasProperty("k8s.testCluster")
|
enabled = project.hasProperty("k8s.testCluster")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
<footer>
|
<footer>
|
||||||
Copyright © Michael N. Lipp 2023
|
Copyright © Michael N. Lipp 2023, 2025
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#
|
#
|
||||||
# VM-Operator
|
# VM-Operator
|
||||||
# Copyright (C) 2023 Michael N. Lipp
|
# Copyright (C) 2025 Michael N. Lipp
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify it
|
# This program is free software; you can redistribute it and/or modify it
|
||||||
# under the terms of the GNU General Public License as published by
|
# under the terms of the GNU General Public License as published by
|
||||||
|
|
@ -19,10 +19,7 @@
|
||||||
handlers=java.util.logging.ConsoleHandler, \
|
handlers=java.util.logging.ConsoleHandler, \
|
||||||
org.jgrapes.webconlet.logviewer.LogViewerHandler
|
org.jgrapes.webconlet.logviewer.LogViewerHandler
|
||||||
|
|
||||||
org.jgrapes.level=FINE
|
org.jdrupes.vmoperator.level=FINE
|
||||||
org.jgrapes.core.handlerTracking.level=FINER
|
|
||||||
|
|
||||||
org.jdrupes.vmoperator.manager.level=FINE
|
|
||||||
|
|
||||||
java.util.logging.ConsoleHandler.level=ALL
|
java.util.logging.ConsoleHandler.level=ALL
|
||||||
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
|
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
namespace: ${ cr.metadata.namespace.asString }
|
namespace: ${ cr.namespace() }
|
||||||
name: ${ cr.metadata.name.asString }
|
name: ${ cr.name() }
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: ${ constants.APP_NAME }
|
app.kubernetes.io/name: ${ constants.APP_NAME }
|
||||||
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
|
app.kubernetes.io/instance: ${ cr.name() }
|
||||||
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
|
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
|
||||||
annotations:
|
annotations:
|
||||||
vmoperator.jdrupes.org/version: ${ managerVersion }
|
vmoperator.jdrupes.org/version: ${ managerVersion }
|
||||||
ownerReferences:
|
ownerReferences:
|
||||||
- apiVersion: ${ cr.apiVersion.asString }
|
- apiVersion: ${ cr.apiVersion() }
|
||||||
kind: ${ constants.VM_OP_KIND_VM }
|
kind: ${ constants.Crd.KIND_VM }
|
||||||
name: ${ cr.metadata.name.asString }
|
name: ${ cr.name() }
|
||||||
uid: ${ cr.metadata.uid.asString }
|
uid: ${ cr.metadata().getUid() }
|
||||||
controller: false
|
controller: false
|
||||||
|
|
||||||
data:
|
data:
|
||||||
|
|
@ -21,115 +21,118 @@ data:
|
||||||
"/Runner":
|
"/Runner":
|
||||||
# The directory used to store data files. Defaults to (depending on
|
# The directory used to store data files. Defaults to (depending on
|
||||||
# values available):
|
# values available):
|
||||||
# * $XDG_DATA_HOME/vmrunner/${ cr.metadata.name.asString }
|
# * $XDG_DATA_HOME/vmrunner/${ cr.name() }
|
||||||
# * $HOME/.local/share/vmrunner/${ cr.metadata.name.asString }
|
# * $HOME/.local/share/vmrunner/${ cr.name() }
|
||||||
# * ./${ cr.metadata.name.asString }
|
# * ./${ cr.name() }
|
||||||
dataDir: /var/local/vm-data
|
dataDir: /var/local/vm-data
|
||||||
|
|
||||||
# The directory used to store runtime files. Defaults to (depending on
|
# The directory used to store runtime files. Defaults to (depending on
|
||||||
# values available):
|
# values available):
|
||||||
# * $XDG_RUNTIME_DIR/vmrunner/${ cr.metadata.name.asString }
|
# * $XDG_RUNTIME_DIR/vmrunner/${ cr.name() }
|
||||||
# * /tmp/$USER/vmrunner/${ cr.metadata.name.asString }
|
# * /tmp/$USER/vmrunner/${ cr.name() }
|
||||||
# * /tmp/vmrunner/${ cr.metadata.name.asString }
|
# * /tmp/vmrunner/${ cr.name() }
|
||||||
# runtimeDir: "$XDG_RUNTIME_DIR/vmrunner/${ cr.metadata.name.asString }"
|
# runtimeDir: "$XDG_RUNTIME_DIR/vmrunner/${ cr.name() }"
|
||||||
|
|
||||||
|
<#assign spec = cr.spec() />
|
||||||
# The template to use. Resolved relative to /usr/share/vmrunner/templates.
|
# The template to use. Resolved relative to /usr/share/vmrunner/templates.
|
||||||
# template: "Standard-VM-latest.ftl.yaml"
|
# template: "Standard-VM-latest.ftl.yaml"
|
||||||
<#if cr.spec.runnerTemplate?? && cr.spec.runnerTemplate.source?? >
|
<#if spec.runnerTemplate?? && spec.runnerTemplate.source?? >
|
||||||
template: ${ cr.spec.runnerTemplate.source.asString }
|
template: ${ spec.runnerTemplate.source }
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
# The template is copied to the data diretory when the VM starts for
|
# The template is copied to the data diretory when the VM starts for
|
||||||
# the first time. Subsequent starts use the copy unless this option is set.
|
# the first time. Subsequent starts use the copy unless this option is set.
|
||||||
<#if cr.spec.runnerTemplate?? && cr.spec.runnerTemplate.update?? >
|
<#if spec.runnerTemplate?? && spec.runnerTemplate.update?? >
|
||||||
updateTemplate: ${ cr.spec.runnerTemplate.update.asBoolean?c }
|
updateTemplate: ${ spec.runnerTemplate.update?c }
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
# Whether a shutdown initiated by the guest stops the pod deployment
|
# Whether a shutdown initiated by the guest stops the pod deployment
|
||||||
guestShutdownStops: ${ cr.spec.guestShutdownStops!false?c }
|
guestShutdownStops: ${ (spec.guestShutdownStops!false)?c }
|
||||||
|
|
||||||
|
# When incremented, the VM is reset. The value has no default value,
|
||||||
|
# i.e. if you start the VM without a value for this property, and
|
||||||
|
# decide to trigger a reset later, you have to first set the value
|
||||||
|
# and then inrement it.
|
||||||
|
resetCounter: ${ cr.extra().resetCount()?c }
|
||||||
|
|
||||||
# Forward the cloud-init data if provided
|
# Forward the cloud-init data if provided
|
||||||
<#if cr.spec.cloudInit??>
|
<#if spec.cloudInit??>
|
||||||
cloudInit:
|
cloudInit:
|
||||||
<#if cr.spec.cloudInit.metaData??>
|
metaData: ${ toJson(adjustCloudInitMeta(spec.cloudInit.metaData!{}, cr.metadata())) }
|
||||||
metaData: ${ cr.spec.cloudInit.metaData.toString() }
|
<#if spec.cloudInit.userData??>
|
||||||
<#else>
|
userData: ${ toJson(spec.cloudInit.userData) }
|
||||||
metaData: {}
|
|
||||||
</#if>
|
|
||||||
<#if cr.spec.cloudInit.userData??>
|
|
||||||
userData: ${ cr.spec.cloudInit.userData.toString() }
|
|
||||||
<#else>
|
<#else>
|
||||||
userData: {}
|
userData: {}
|
||||||
</#if>
|
</#if>
|
||||||
<#if cr.spec.cloudInit.networkConfig??>
|
<#if spec.cloudInit.networkConfig??>
|
||||||
networkConfig: ${ cr.spec.cloudInit.networkConfig.toString() }
|
networkConfig: ${ toJson(spec.cloudInit.networkConfig) }
|
||||||
</#if>
|
</#if>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
# Define the VM (required)
|
# Define the VM (required)
|
||||||
vm:
|
vm:
|
||||||
# The VM's name (required)
|
# The VM's name (required)
|
||||||
name: ${ cr.metadata.name.asString }
|
name: ${ cr.name() }
|
||||||
|
|
||||||
# The machine's uuid. If none is specified, a uuid is generated
|
# The machine's uuid. If none is specified, a uuid is generated
|
||||||
# and stored in the data directory. If the uuid is important
|
# and stored in the data directory. If the uuid is important
|
||||||
# (e.g. because licenses depend on it) it is recommaned to specify
|
# (e.g. because licenses depend on it) it is recommaned to specify
|
||||||
# it here explicitly or to carefully backup the data directory.
|
# it here explicitly or to carefully backup the data directory.
|
||||||
# uuid: "generated uuid"
|
# uuid: "generated uuid"
|
||||||
<#if cr.spec.vm.machineUuid??>
|
<#if spec.vm.machineUuid??>
|
||||||
uuid: "${ cr.spec.vm.machineUuid.asString }"
|
uuid: "${ spec.vm.machineUuid }"
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
# Whether to provide a software TPM (defaults to false)
|
# Whether to provide a software TPM (defaults to false)
|
||||||
# useTpm: false
|
# useTpm: false
|
||||||
useTpm: ${ cr.spec.vm.useTpm.asBoolean?c }
|
useTpm: ${ spec.vm.useTpm?c }
|
||||||
|
|
||||||
# How to boot (see https://github.com/mnlipp/VM-Operator/blob/main/org.jdrupes.vmoperator.runner.qemu/resources/org/jdrupes/vmoperator/runner/qemu/defaults.yaml):
|
# How to boot (see https://github.com/mnlipp/VM-Operator/blob/main/org.jdrupes.vmoperator.runner.qemu/resources/org/jdrupes/vmoperator/runner/qemu/defaults.yaml):
|
||||||
# * bios
|
# * bios
|
||||||
# * uefi[-4m]
|
# * uefi[-4m]
|
||||||
# * secure[-4m]
|
# * secure[-4m]
|
||||||
firmware: ${ cr.spec.vm.firmware.asString }
|
firmware: ${ spec.vm.firmware }
|
||||||
|
|
||||||
# Whether to show a boot menu.
|
# Whether to show a boot menu.
|
||||||
# bootMenu: false
|
# bootMenu: false
|
||||||
bootMenu: ${ cr.spec.vm.bootMenu.asBoolean?c }
|
bootMenu: ${ spec.vm.bootMenu?c }
|
||||||
|
|
||||||
# When terminating, a graceful powerdown is attempted. If it
|
# When terminating, a graceful powerdown is attempted. If it
|
||||||
# doesn't succeed within the given timeout (seconds) SIGTERM
|
# doesn't succeed within the given timeout (seconds) SIGTERM
|
||||||
# is sent to Qemu.
|
# is sent to Qemu.
|
||||||
# powerdownTimeout: 900
|
# powerdownTimeout: 900
|
||||||
powerdownTimeout: ${ cr.spec.vm.powerdownTimeout.asLong?c }
|
powerdownTimeout: ${ spec.vm.powerdownTimeout?c }
|
||||||
|
|
||||||
# CPU settings
|
# CPU settings
|
||||||
cpuModel: ${ cr.spec.vm.cpuModel.asString }
|
cpuModel: ${ spec.vm.cpuModel }
|
||||||
# Setting maximumCpus to 1 omits the "-smp" options. The defaults (0)
|
# Setting maximumCpus to 1 omits the "-smp" options. The defaults (0)
|
||||||
# cause the corresponding property to be omitted from the "-smp" option.
|
# cause the corresponding property to be omitted from the "-smp" option.
|
||||||
# If currentCpus is greater than maximumCpus, the latter is adjusted.
|
# If currentCpus is greater than maximumCpus, the latter is adjusted.
|
||||||
<#if cr.spec.vm.maximumCpus?? >
|
<#if spec.vm.maximumCpus?? >
|
||||||
maximumCpus: ${ parseQuantity(cr.spec.vm.maximumCpus.asString)?c }
|
maximumCpus: ${ parseQuantity(spec.vm.maximumCpus)?c }
|
||||||
</#if>
|
</#if>
|
||||||
<#if cr.spec.vm.cpuTopology?? >
|
<#if spec.vm.cpuTopology?? >
|
||||||
sockets: ${ cr.spec.vm.cpuTopology.sockets.asInt?c }
|
sockets: ${ spec.vm.cpuTopology.sockets?c }
|
||||||
diesPerSocket: ${ cr.spec.vm.cpuTopology.diesPerSocket.asInt?c }
|
diesPerSocket: ${ spec.vm.cpuTopology.diesPerSocket?c }
|
||||||
coresPerDie: ${ cr.spec.vm.cpuTopology.coresPerDie.asInt?c }
|
coresPerDie: ${ spec.vm.cpuTopology.coresPerDie?c }
|
||||||
threadsPerCore: ${ cr.spec.vm.cpuTopology.threadsPerCore.asInt?c }
|
threadsPerCore: ${ spec.vm.cpuTopology.threadsPerCore?c }
|
||||||
</#if>
|
</#if>
|
||||||
<#if cr.spec.vm.currentCpus?? >
|
<#if spec.vm.currentCpus?? >
|
||||||
currentCpus: ${ parseQuantity(cr.spec.vm.currentCpus.asString)?c }
|
currentCpus: ${ parseQuantity(spec.vm.currentCpus)?c }
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
# RAM settings
|
# RAM settings
|
||||||
# Maximum defaults to 1G
|
# Maximum defaults to 1G
|
||||||
maximumRam: "${ formatMemory(parseQuantity(cr.spec.vm.maximumRam.asString)) }"
|
maximumRam: "${ formatMemory(parseQuantity(spec.vm.maximumRam)) }"
|
||||||
<#if cr.spec.vm.currentRam?? >
|
<#if spec.vm.currentRam?? >
|
||||||
currentRam: "${ formatMemory(parseQuantity(cr.spec.vm.currentRam.asString)) }"
|
currentRam: "${ formatMemory(parseQuantity(spec.vm.currentRam)) }"
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
# RTC settings.
|
# RTC settings.
|
||||||
# rtcBase: utc
|
# rtcBase: utc
|
||||||
# rtcClock: rt
|
# rtcClock: rt
|
||||||
rtcBase: ${ cr.spec.vm.rtcBase.asString }
|
rtcBase: ${ spec.vm.rtcBase }
|
||||||
rtcClock: ${ cr.spec.vm.rtcClock.asString }
|
rtcClock: ${ spec.vm.rtcClock }
|
||||||
|
|
||||||
# Network settings
|
# Network settings
|
||||||
# Supported types are "tap" and "user" (for debugging). Type "user"
|
# Supported types are "tap" and "user" (for debugging). Type "user"
|
||||||
|
|
@ -141,19 +144,19 @@ data:
|
||||||
# mac: (undefined)
|
# mac: (undefined)
|
||||||
network:
|
network:
|
||||||
<#assign nwCounter = 0/>
|
<#assign nwCounter = 0/>
|
||||||
<#list cr.spec.vm.networks.asList() as itf>
|
<#list spec.vm.networks as itf>
|
||||||
<#if itf.tap??>
|
<#if itf.tap??>
|
||||||
- type: tap
|
- type: tap
|
||||||
device: ${ itf.tap.device.asString }
|
device: ${ itf.tap.device }
|
||||||
bridge: ${ itf.tap.bridge.asString }
|
bridge: ${ itf.tap.bridge }
|
||||||
<#if itf.tap.mac??>
|
<#if itf.tap.mac??>
|
||||||
mac: "${ itf.tap.mac.asString }"
|
mac: "${ itf.tap.mac }"
|
||||||
</#if>
|
</#if>
|
||||||
<#elseif itf.user??>
|
<#elseif itf.user??>
|
||||||
- type: user
|
- type: user
|
||||||
device: ${ itf.user.device.asString }
|
device: ${ itf.user.device }
|
||||||
<#if itf.user.net??>
|
<#if itf.user.net??>
|
||||||
net: "${ itf.user.net.asString }"
|
net: "${ itf.user.net }"
|
||||||
</#if>
|
</#if>
|
||||||
</#if>
|
</#if>
|
||||||
<#assign nwCounter += 1/>
|
<#assign nwCounter += 1/>
|
||||||
|
|
@ -169,11 +172,11 @@ data:
|
||||||
# file: (undefined)
|
# file: (undefined)
|
||||||
drives:
|
drives:
|
||||||
<#assign drvCounter = 0/>
|
<#assign drvCounter = 0/>
|
||||||
<#list cr.spec.vm.disks.asList() as disk>
|
<#list spec.vm.disks as disk>
|
||||||
<#if disk.volumeClaimTemplate??
|
<#if disk.volumeClaimTemplate??
|
||||||
&& disk.volumeClaimTemplate.metadata??
|
&& disk.volumeClaimTemplate.metadata??
|
||||||
&& disk.volumeClaimTemplate.metadata.name??>
|
&& disk.volumeClaimTemplate.metadata.name??>
|
||||||
<#assign diskName = disk.volumeClaimTemplate.metadata.name.asString + "-disk">
|
<#assign diskName = disk.volumeClaimTemplate.metadata.name + "-disk">
|
||||||
<#else>
|
<#else>
|
||||||
<#assign diskName = "disk-" + drvCounter>
|
<#assign diskName = "disk-" + drvCounter>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
@ -181,30 +184,36 @@ data:
|
||||||
- type: raw
|
- type: raw
|
||||||
resource: /dev/${ diskName }
|
resource: /dev/${ diskName }
|
||||||
<#if disk.bootindex??>
|
<#if disk.bootindex??>
|
||||||
bootindex: ${ disk.bootindex.asInt?c }
|
bootindex: ${ disk.bootindex?c }
|
||||||
</#if>
|
</#if>
|
||||||
<#assign drvCounter = drvCounter + 1/>
|
<#assign drvCounter = drvCounter + 1/>
|
||||||
</#if>
|
</#if>
|
||||||
<#if disk.cdrom??>
|
<#if disk.cdrom??>
|
||||||
- type: ide-cd
|
- type: ide-cd
|
||||||
file: "${ disk.cdrom.image.asString }"
|
file: "${ imageLocation(disk.cdrom.image) }"
|
||||||
<#if disk.bootindex??>
|
<#if disk.bootindex??>
|
||||||
bootindex: ${ disk.bootindex.asInt?c }
|
bootindex: ${ disk.bootindex?c }
|
||||||
</#if>
|
</#if>
|
||||||
</#if>
|
</#if>
|
||||||
</#list>
|
</#list>
|
||||||
|
|
||||||
display:
|
display:
|
||||||
<#if cr.spec.vm.display.spice??>
|
<#if spec.vm.display.outputs?? >
|
||||||
|
outputs: ${ spec.vm.display.outputs?c }
|
||||||
|
</#if>
|
||||||
|
<#if loginRequestedFor?? >
|
||||||
|
loggedInUser: "${ loginRequestedFor }"
|
||||||
|
</#if>
|
||||||
|
<#if spec.vm.display.spice??>
|
||||||
spice:
|
spice:
|
||||||
port: ${ cr.spec.vm.display.spice.port.asInt?c }
|
port: ${ spec.vm.display.spice.port?c }
|
||||||
<#if cr.spec.vm.display.spice.ticket??>
|
<#if spec.vm.display.spice.ticket??>
|
||||||
ticket: "${ cr.spec.vm.display.spice.ticket.asString }"
|
ticket: "${ spec.vm.display.spice.ticket }"
|
||||||
</#if>
|
</#if>
|
||||||
<#if cr.spec.vm.display.spice.streamingVideo??>
|
<#if spec.vm.display.spice.streamingVideo??>
|
||||||
streaming-video: "${ cr.spec.vm.display.spice.streamingVideo.asString }"
|
streaming-video: "${ spec.vm.display.spice.streamingVideo }"
|
||||||
</#if>
|
</#if>
|
||||||
usbRedirects: ${ cr.spec.vm.display.spice.usbRedirects.asInt?c }
|
usbRedirects: ${ spec.vm.display.spice.usbRedirects?c }
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
logging.properties: |
|
logging.properties: |
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: ${ cr.namespace() }
|
||||||
|
name: ${ runnerDataPvcName }
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ${ constants.APP_NAME }
|
||||||
|
app.kubernetes.io/instance: ${ cr.name() }
|
||||||
|
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
<#if reconciler.runnerDataPvc?? && reconciler.runnerDataPvc.storageClassName??>
|
||||||
|
storageClassName: ${ reconciler.runnerDataPvc.storageClassName }
|
||||||
|
</#if>
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Mi
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: ${ cr.namespace() }
|
||||||
|
name: ${ disk.generatedPvcName }
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ${ constants.APP_NAME }
|
||||||
|
app.kubernetes.io/instance: ${ cr.name() }
|
||||||
|
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
|
||||||
|
<#if disk.volumeClaimTemplate.metadata??
|
||||||
|
&& disk.volumeClaimTemplate.metadata.annotations??>
|
||||||
|
annotations:
|
||||||
|
${ toJson(disk.volumeClaimTemplate.metadata.annotations) }
|
||||||
|
</#if>
|
||||||
|
spec:
|
||||||
|
${ toJson(disk.volumeClaimTemplate.spec) }
|
||||||
|
|
@ -1,26 +1,26 @@
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
namespace: ${ cr.metadata.namespace.asString }
|
namespace: ${ cr.namespace() }
|
||||||
name: ${ cr.metadata.name.asString }
|
name: ${ cr.name() }
|
||||||
labels:
|
labels:
|
||||||
app.kubernetes.io/name: ${ constants.APP_NAME }
|
app.kubernetes.io/name: ${ constants.APP_NAME }
|
||||||
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
|
app.kubernetes.io/instance: ${ cr.name() }
|
||||||
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
|
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
|
||||||
annotations:
|
annotations:
|
||||||
vmoperator.jdrupes.org/version: ${ managerVersion }
|
vmoperator.jdrupes.org/version: ${ managerVersion }
|
||||||
ownerReferences:
|
ownerReferences:
|
||||||
- apiVersion: ${ cr.apiVersion.asString }
|
- apiVersion: ${ cr.apiVersion() }
|
||||||
kind: ${ constants.VM_OP_KIND_VM }
|
kind: ${ constants.Crd.KIND_VM }
|
||||||
name: ${ cr.metadata.name.asString }
|
name: ${ cr.name() }
|
||||||
uid: ${ cr.metadata.uid.asString }
|
uid: ${ cr.metadata().getUid() }
|
||||||
controller: false
|
controller: false
|
||||||
|
|
||||||
spec:
|
spec:
|
||||||
type: LoadBalancer
|
type: LoadBalancer
|
||||||
ports:
|
ports:
|
||||||
- name: spice
|
- name: spice
|
||||||
port: ${ cr.spec.vm.display.spice.port.asInt?c }
|
port: ${ cr.spec().vm.display.spice.port?c }
|
||||||
selector:
|
selector:
|
||||||
app.kubernetes.io/name: ${ constants.APP_NAME }
|
app.kubernetes.io/name: ${ constants.APP_NAME }
|
||||||
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
|
app.kubernetes.io/instance: ${ cr.name() }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
kind: Pod
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
namespace: ${ cr.namespace() }
|
||||||
|
name: ${ cr.name() }
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: ${ constants.APP_NAME }
|
||||||
|
app.kubernetes.io/instance: ${ cr.name() }
|
||||||
|
app.kubernetes.io/component: ${ constants.APP_NAME }
|
||||||
|
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
|
||||||
|
annotations:
|
||||||
|
# Triggers update of config map mounted in pod
|
||||||
|
# See https://ahmet.im/blog/kubernetes-secret-volumes-delay/
|
||||||
|
vmrunner.jdrupes.org/cmVersion: "${ configMapResourceVersion }"
|
||||||
|
vmoperator.jdrupes.org/version: ${ managerVersion }
|
||||||
|
ownerReferences:
|
||||||
|
- apiVersion: ${ cr.apiVersion() }
|
||||||
|
kind: ${ constants.Crd.KIND_VM }
|
||||||
|
name: ${ cr.name() }
|
||||||
|
uid: ${ cr.metadata().getUid() }
|
||||||
|
blockOwnerDeletion: true
|
||||||
|
controller: false
|
||||||
|
<#assign spec = cr.spec() />
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: ${ cr.name() }
|
||||||
|
<#assign image = spec.image>
|
||||||
|
<#if image.source??>
|
||||||
|
image: ${ image.source }
|
||||||
|
<#else>
|
||||||
|
image: ${ image.repository }/${ image.path }<#if image.version??>:${ image.version }</#if>
|
||||||
|
</#if>
|
||||||
|
<#if image.pullPolicy??>
|
||||||
|
imagePullPolicy: ${ image.pullPolicy }
|
||||||
|
</#if>
|
||||||
|
<#if spec.vm.display.spice??>
|
||||||
|
ports:
|
||||||
|
<#if spec.vm.display.spice??>
|
||||||
|
- name: spice
|
||||||
|
containerPort: ${ spec.vm.display.spice.port?c }
|
||||||
|
protocol: TCP
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
volumeMounts:
|
||||||
|
# Not needed because pod is priviledged:
|
||||||
|
# - mountPath: /dev/kvm
|
||||||
|
# name: dev-kvm
|
||||||
|
# - mountPath: /dev/net/tun
|
||||||
|
# name: dev-tun
|
||||||
|
# - mountPath: /sys/fs/cgroup
|
||||||
|
# name: cgroup
|
||||||
|
- name: config
|
||||||
|
mountPath: /etc/opt/vmrunner
|
||||||
|
- name: runner-data
|
||||||
|
mountPath: /var/local/vm-data
|
||||||
|
- name: vmop-image-repository
|
||||||
|
mountPath: ${ constants.IMAGE_REPO_PATH }
|
||||||
|
volumeDevices:
|
||||||
|
<#list spec.vm.disks as disk>
|
||||||
|
<#if disk.volumeClaimTemplate??>
|
||||||
|
- name: ${ disk.generatedDiskName }
|
||||||
|
devicePath: /dev/${ disk.generatedDiskName }
|
||||||
|
</#if>
|
||||||
|
</#list>
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
<#if spec.resources??>
|
||||||
|
resources: ${ toJson(spec.resources) }
|
||||||
|
<#else>
|
||||||
|
<#if spec.vm.currentCpus?? || spec.vm.currentRam?? >
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
<#if spec.vm.currentCpus?? >
|
||||||
|
<#assign factor = 2.0 />
|
||||||
|
<#if reconciler.cpuOvercommit??>
|
||||||
|
<#assign factor = reconciler.cpuOvercommit * 1.0 />
|
||||||
|
</#if>
|
||||||
|
cpu: ${ (parseQuantity(spec.vm.currentCpus) / factor)?c }
|
||||||
|
</#if>
|
||||||
|
<#if spec.vm.currentRam?? >
|
||||||
|
<#assign factor = 1.25 />
|
||||||
|
<#if reconciler.ramOvercommit??>
|
||||||
|
<#assign factor = reconciler.ramOvercommit * 1.0 />
|
||||||
|
</#if>
|
||||||
|
memory: ${ (parseQuantity(spec.vm.currentRam) / factor)?floor?c }
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
</#if>
|
||||||
|
volumes:
|
||||||
|
# Not needed because pod is priviledged:
|
||||||
|
# - name: dev-kvm
|
||||||
|
# hostPath:
|
||||||
|
# path: /dev/kvm
|
||||||
|
# type: CharDevice
|
||||||
|
# - hostPath:
|
||||||
|
# path: /dev/net/tun
|
||||||
|
# type: CharDevice
|
||||||
|
# name: dev-tun
|
||||||
|
# - name: cgroup
|
||||||
|
# hostPath:
|
||||||
|
# path: /sys/fs/cgroup
|
||||||
|
- name: config
|
||||||
|
projected:
|
||||||
|
sources:
|
||||||
|
- configMap:
|
||||||
|
name: ${ cr.name() }
|
||||||
|
<#if displaySecret??>
|
||||||
|
- secret:
|
||||||
|
name: ${ displaySecret }
|
||||||
|
</#if>
|
||||||
|
- name: vmop-image-repository
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: vmop-image-repository
|
||||||
|
- name: runner-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: ${ runnerDataPvcName }
|
||||||
|
<#list spec.vm.disks as disk>
|
||||||
|
<#if disk.volumeClaimTemplate??>
|
||||||
|
- name: ${ disk.generatedDiskName }
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: ${ disk.generatedPvcName }
|
||||||
|
</#if>
|
||||||
|
</#list>
|
||||||
|
hostNetwork: true
|
||||||
|
terminationGracePeriodSeconds: ${ (spec.vm.powerdownTimeout + 5)?c }
|
||||||
|
<#if spec.nodeName??>
|
||||||
|
nodeName: ${ spec.nodeName }
|
||||||
|
</#if>
|
||||||
|
<#if spec.nodeSelector??>
|
||||||
|
nodeSelector: ${ toJson(spec.nodeSelector) }
|
||||||
|
</#if>
|
||||||
|
<#if spec.affinity??>
|
||||||
|
affinity: ${ toJson(spec.affinity) }
|
||||||
|
</#if>
|
||||||
|
serviceAccountName: vm-runner
|
||||||
|
|
@ -1,194 +0,0 @@
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: StatefulSet
|
|
||||||
metadata:
|
|
||||||
namespace: ${ cr.metadata.namespace.asString }
|
|
||||||
name: ${ cr.metadata.name.asString }
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: ${ constants.APP_NAME }
|
|
||||||
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
|
|
||||||
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
|
|
||||||
annotations:
|
|
||||||
vmoperator.jdrupes.org/version: ${ managerVersion }
|
|
||||||
ownerReferences:
|
|
||||||
- apiVersion: ${ cr.apiVersion.asString }
|
|
||||||
kind: ${ constants.VM_OP_KIND_VM }
|
|
||||||
name: ${ cr.metadata.name.asString }
|
|
||||||
uid: ${ cr.metadata.uid.asString }
|
|
||||||
blockOwnerDeletion: true
|
|
||||||
controller: false
|
|
||||||
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app.kubernetes.io/name: ${ constants.APP_NAME }
|
|
||||||
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
|
|
||||||
replicas: ${ (cr.spec.vm.state.asString == "Running")?then(1, 0) }
|
|
||||||
updateStrategy:
|
|
||||||
type: OnDelete
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
namespace: ${ cr.metadata.namespace.asString }
|
|
||||||
name: ${ cr.metadata.name.asString }
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: ${ constants.APP_NAME }
|
|
||||||
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
|
|
||||||
app.kubernetes.io/component: ${ constants.APP_NAME }
|
|
||||||
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
|
|
||||||
annotations:
|
|
||||||
# Triggers update of config map mounted in pod
|
|
||||||
# See https://ahmet.im/blog/kubernetes-secret-volumes-delay/
|
|
||||||
vmrunner.jdrupes.org/cmVersion: "${ cm.metadata.resourceVersion.asString }"
|
|
||||||
vmoperator.jdrupes.org/version: ${ managerVersion }
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: ${ cr.metadata.name.asString }
|
|
||||||
<#assign image = cr.spec.image>
|
|
||||||
<#if image.source??>
|
|
||||||
image: ${ image.source.asString }
|
|
||||||
<#else>
|
|
||||||
image: ${ image.repository.asString }/${ image.path.asString }<#if image.version??>:${ image.version.asString }</#if>
|
|
||||||
</#if>
|
|
||||||
<#if image.pullPolicy??>
|
|
||||||
imagePullPolicy: ${ image.pullPolicy.asString }
|
|
||||||
</#if>
|
|
||||||
<#if cr.spec.vm.display.spice??>
|
|
||||||
ports:
|
|
||||||
<#if cr.spec.vm.display.spice??>
|
|
||||||
- name: spice
|
|
||||||
containerPort: ${ cr.spec.vm.display.spice.port.asInt?c }
|
|
||||||
protocol: TCP
|
|
||||||
</#if>
|
|
||||||
</#if>
|
|
||||||
volumeMounts:
|
|
||||||
# Not needed because pod is priviledged:
|
|
||||||
# - mountPath: /dev/kvm
|
|
||||||
# name: dev-kvm
|
|
||||||
# - mountPath: /dev/net/tun
|
|
||||||
# name: dev-tun
|
|
||||||
# - mountPath: /sys/fs/cgroup
|
|
||||||
# name: cgroup
|
|
||||||
- name: config
|
|
||||||
mountPath: /etc/opt/vmrunner
|
|
||||||
- name: runner-data
|
|
||||||
mountPath: /var/local/vm-data
|
|
||||||
- name: vmop-image-repository
|
|
||||||
mountPath: ${ constants.IMAGE_REPO_PATH }
|
|
||||||
volumeDevices:
|
|
||||||
<#assign diskCounter = 0/>
|
|
||||||
<#list cr.spec.vm.disks.asList() as disk>
|
|
||||||
<#if disk.volumeClaimTemplate??>
|
|
||||||
<#if disk.volumeClaimTemplate.metadata??
|
|
||||||
&& disk.volumeClaimTemplate.metadata.name??>
|
|
||||||
<#assign diskName = disk.volumeClaimTemplate.metadata.name.asString + "-disk">
|
|
||||||
<#else>
|
|
||||||
<#assign diskName = "disk-" + diskCounter>
|
|
||||||
</#if>
|
|
||||||
- name: ${ diskName }
|
|
||||||
devicePath: /dev/${ diskName }
|
|
||||||
<#assign diskCounter = diskCounter + 1/>
|
|
||||||
</#if>
|
|
||||||
</#list>
|
|
||||||
securityContext:
|
|
||||||
privileged: true
|
|
||||||
<#if cr.spec.resources??>
|
|
||||||
resources: ${ cr.spec.resources.toString() }
|
|
||||||
<#else>
|
|
||||||
<#if cr.spec.vm.currentCpus?? || cr.spec.vm.currentRam?? >
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
<#if cr.spec.vm.currentCpus?? >
|
|
||||||
<#assign factor = 2.0 />
|
|
||||||
<#if reconciler.cpuOvercommit??>
|
|
||||||
<#assign factor = reconciler.cpuOvercommit * 1.0 />
|
|
||||||
</#if>
|
|
||||||
cpu: ${ (parseQuantity(cr.spec.vm.currentCpus.asString) / factor)?c }
|
|
||||||
</#if>
|
|
||||||
<#if cr.spec.vm.currentRam?? >
|
|
||||||
<#assign factor = 1.25 />
|
|
||||||
<#if reconciler.ramOvercommit??>
|
|
||||||
<#assign factor = reconciler.ramOvercommit * 1.0 />
|
|
||||||
</#if>
|
|
||||||
memory: ${ (parseQuantity(cr.spec.vm.currentRam.asString) / factor)?floor?c }
|
|
||||||
</#if>
|
|
||||||
</#if>
|
|
||||||
</#if>
|
|
||||||
volumes:
|
|
||||||
# Not needed because pod is priviledged:
|
|
||||||
# - name: dev-kvm
|
|
||||||
# hostPath:
|
|
||||||
# path: /dev/kvm
|
|
||||||
# type: CharDevice
|
|
||||||
# - hostPath:
|
|
||||||
# path: /dev/net/tun
|
|
||||||
# type: CharDevice
|
|
||||||
# name: dev-tun
|
|
||||||
# - name: cgroup
|
|
||||||
# hostPath:
|
|
||||||
# path: /sys/fs/cgroup
|
|
||||||
- name: config
|
|
||||||
projected:
|
|
||||||
sources:
|
|
||||||
- configMap:
|
|
||||||
name: ${ cr.metadata.name.asString }
|
|
||||||
<#if displaySecret??>
|
|
||||||
- secret:
|
|
||||||
name: ${ displaySecret }
|
|
||||||
</#if>
|
|
||||||
- name: vmop-image-repository
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: vmop-image-repository
|
|
||||||
hostNetwork: true
|
|
||||||
terminationGracePeriodSeconds: ${ (cr.spec.vm.powerdownTimeout.asInt + 5)?c }
|
|
||||||
<#if cr.spec.nodeName??>
|
|
||||||
nodeName: ${ cr.spec.nodeName.asString }
|
|
||||||
</#if>
|
|
||||||
<#if cr.spec.nodeSelector??>
|
|
||||||
nodeSelector: ${ cr.spec.nodeSelector.toString() }
|
|
||||||
</#if>
|
|
||||||
<#if cr.spec.affinity??>
|
|
||||||
affinity: ${ cr.spec.affinity.toString() }
|
|
||||||
</#if>
|
|
||||||
serviceAccountName: vm-runner
|
|
||||||
volumeClaimTemplates:
|
|
||||||
- metadata:
|
|
||||||
namespace: ${ cr.metadata.namespace.asString }
|
|
||||||
name: runner-data
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: ${ constants.APP_NAME }
|
|
||||||
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
|
|
||||||
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
<#if reconciler.runnerDataPvc?? && reconciler.runnerDataPvc.storageClassName??>
|
|
||||||
storageClassName: ${ reconciler.runnerDataPvc.storageClassName }
|
|
||||||
</#if>
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 1Mi
|
|
||||||
<#assign diskCounter = 0/>
|
|
||||||
<#list cr.spec.vm.disks.asList() as disk>
|
|
||||||
<#if disk.volumeClaimTemplate??>
|
|
||||||
<#if disk.volumeClaimTemplate.metadata??
|
|
||||||
&& disk.volumeClaimTemplate.metadata.name??>
|
|
||||||
<#assign diskName = disk.volumeClaimTemplate.metadata.name.asString + "-disk">
|
|
||||||
<#else>
|
|
||||||
<#assign diskName = "disk-" + diskCounter>
|
|
||||||
</#if>
|
|
||||||
- metadata:
|
|
||||||
namespace: ${ cr.metadata.namespace.asString }
|
|
||||||
name: ${ diskName }
|
|
||||||
labels:
|
|
||||||
app.kubernetes.io/name: ${ constants.APP_NAME }
|
|
||||||
app.kubernetes.io/instance: ${ cr.metadata.name.asString }
|
|
||||||
app.kubernetes.io/managed-by: ${ constants.VM_OP_NAME }
|
|
||||||
<#if disk.volumeClaimTemplate.metadata??
|
|
||||||
&& disk.volumeClaimTemplate.metadata.annotations??>
|
|
||||||
annotations:
|
|
||||||
${ disk.volumeClaimTemplate.metadata.annotations.toString() }
|
|
||||||
</#if>
|
|
||||||
spec:
|
|
||||||
${ disk.volumeClaimTemplate.spec.toString() }
|
|
||||||
<#assign diskCounter = diskCounter + 1/>
|
|
||||||
</#if>
|
|
||||||
</#list>
|
|
||||||
|
|
@ -27,14 +27,11 @@ import io.kubernetes.client.util.generic.options.ListOptions;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import org.jdrupes.vmoperator.common.K8s;
|
import org.jdrupes.vmoperator.common.K8s;
|
||||||
import org.jdrupes.vmoperator.common.K8sClient;
|
import org.jdrupes.vmoperator.common.K8sClient;
|
||||||
import org.jdrupes.vmoperator.common.K8sObserver;
|
import org.jdrupes.vmoperator.common.K8sObserver;
|
||||||
import org.jdrupes.vmoperator.common.K8sObserver.ResponseType;
|
|
||||||
import org.jdrupes.vmoperator.manager.events.ChannelManager;
|
|
||||||
import org.jdrupes.vmoperator.manager.events.Exit;
|
import org.jdrupes.vmoperator.manager.events.Exit;
|
||||||
import org.jgrapes.core.Channel;
|
import org.jgrapes.core.Channel;
|
||||||
import org.jgrapes.core.Component;
|
import org.jgrapes.core.Component;
|
||||||
|
|
@ -45,12 +42,15 @@ import org.jgrapes.core.events.Stop;
|
||||||
import org.jgrapes.util.events.ConfigurationUpdate;
|
import org.jgrapes.util.events.ConfigurationUpdate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A base class for monitoring VM related resources.
|
* A base class for monitoring VM related resources. When started,
|
||||||
|
* it creates observers for all versions of the the {@link APIResource}
|
||||||
|
* configured by {@link #context(APIResource)}. The APIResource is not
|
||||||
|
* passed to the constructor because in some cases it has to be
|
||||||
|
* evaluated lazily.
|
||||||
*
|
*
|
||||||
* @param <O> the object type for the context
|
* @param <O> the object type for the context
|
||||||
* @param <L> the object list type for the context
|
* @param <L> the object list type for the context
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis" })
|
|
||||||
public abstract class AbstractMonitor<O extends KubernetesObject,
|
public abstract class AbstractMonitor<O extends KubernetesObject,
|
||||||
L extends KubernetesListObject, C extends Channel> extends Component {
|
L extends KubernetesListObject, C extends Channel> extends Component {
|
||||||
|
|
||||||
|
|
@ -61,16 +61,17 @@ public abstract class AbstractMonitor<O extends KubernetesObject,
|
||||||
private String namespace;
|
private String namespace;
|
||||||
private ListOptions options = new ListOptions();
|
private ListOptions options = new ListOptions();
|
||||||
private final AtomicInteger observerCounter = new AtomicInteger(0);
|
private final AtomicInteger observerCounter = new AtomicInteger(0);
|
||||||
private ChannelManager<String, C, ?> channelManager;
|
|
||||||
private boolean channelManagerMaster;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the instance.
|
* Initializes the instance.
|
||||||
*
|
*
|
||||||
* @param componentChannel the component channel
|
* @param componentChannel the component channel
|
||||||
|
* @param objectClass the class of the Kubernetes object to watch
|
||||||
|
* @param objectListClass the class of the list of Kubernetes objects
|
||||||
|
* to watch
|
||||||
*/
|
*/
|
||||||
protected AbstractMonitor(Channel componentChannel, Class<O> objectClass,
|
protected AbstractMonitor(Channel componentChannel,
|
||||||
Class<L> objectListClass) {
|
Class<O> objectClass, Class<L> objectListClass) {
|
||||||
super(componentChannel);
|
super(componentChannel);
|
||||||
this.objectClass = objectClass;
|
this.objectClass = objectClass;
|
||||||
this.objectListClass = objectListClass;
|
this.objectListClass = objectListClass;
|
||||||
|
|
@ -156,27 +157,6 @@ public abstract class AbstractMonitor<O extends KubernetesObject,
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the channel manager.
|
|
||||||
*
|
|
||||||
* @return the context
|
|
||||||
*/
|
|
||||||
public ChannelManager<String, C, ?> channelManager() {
|
|
||||||
return channelManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the channel manager.
|
|
||||||
*
|
|
||||||
* @param channelManager the channel manager
|
|
||||||
* @return the abstract monitor
|
|
||||||
*/
|
|
||||||
public AbstractMonitor<O, L, C>
|
|
||||||
channelManager(ChannelManager<String, C, ?> channelManager) {
|
|
||||||
this.channelManager = channelManager;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Looks for a key "namespace" in the configuration and, if found,
|
* Looks for a key "namespace" in the configuration and, if found,
|
||||||
* sets the namespace to its value.
|
* sets the namespace to its value.
|
||||||
|
|
@ -194,13 +174,12 @@ public abstract class AbstractMonitor<O extends KubernetesObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the start event. Configures the namespace invokes
|
* Handle the start event. Configures the namespace, invokes
|
||||||
* {@link #prepareMonitoring()} and starts the observers.
|
* {@link #prepareMonitoring()} and starts the observers.
|
||||||
*
|
*
|
||||||
* @param event the event
|
* @param event the event
|
||||||
*/
|
*/
|
||||||
@Handler(priority = 10)
|
@Handler(priority = 10)
|
||||||
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
|
|
||||||
public void onStart(Start event) {
|
public void onStart(Start event) {
|
||||||
try {
|
try {
|
||||||
// Get namespace
|
// Get namespace
|
||||||
|
|
@ -218,8 +197,6 @@ public abstract class AbstractMonitor<O extends KubernetesObject,
|
||||||
assert client != null;
|
assert client != null;
|
||||||
assert context != null;
|
assert context != null;
|
||||||
assert namespace != null;
|
assert namespace != null;
|
||||||
logger.fine(() -> "Observing " + K8s.toString(context)
|
|
||||||
+ " objects in " + namespace);
|
|
||||||
|
|
||||||
// Monitor all versions
|
// Monitor all versions
|
||||||
for (var version : context.getVersions()) {
|
for (var version : context.getVersions()) {
|
||||||
|
|
@ -238,13 +215,7 @@ public abstract class AbstractMonitor<O extends KubernetesObject,
|
||||||
observerCounter.incrementAndGet();
|
observerCounter.incrementAndGet();
|
||||||
new K8sObserver<>(objectClass, objectListClass, client,
|
new K8sObserver<>(objectClass, objectListClass, client,
|
||||||
K8s.preferred(context, version), namespace, options)
|
K8s.preferred(context, version), namespace, options)
|
||||||
.handler((c, r) -> {
|
.handler(this::handleChange).onTerminated((o, t) -> {
|
||||||
handleChange(c, r);
|
|
||||||
if (ResponseType.valueOf(r.type) == ResponseType.DELETED
|
|
||||||
&& channelManagerMaster) {
|
|
||||||
channelManager.remove(r.object.getMetadata().getName());
|
|
||||||
}
|
|
||||||
}).onTerminated((o, t) -> {
|
|
||||||
if (observerCounter.decrementAndGet() == 0) {
|
if (observerCounter.decrementAndGet() == 0) {
|
||||||
unregisterAsGenerator();
|
unregisterAsGenerator();
|
||||||
}
|
}
|
||||||
|
|
@ -257,7 +228,8 @@ public abstract class AbstractMonitor<O extends KubernetesObject,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked by {@link #onStart(Start)} after the namespace has
|
* Invoked by {@link #onStart(Start)} after the namespace has
|
||||||
* been configured and before starting the observer.
|
* been configured and before starting the observer. This is
|
||||||
|
* the last opportunity to invoke {@link #context(APIResource)}.
|
||||||
*
|
*
|
||||||
* @throws IOException Signals that an I/O exception has occurred.
|
* @throws IOException Signals that an I/O exception has occurred.
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the api exception
|
||||||
|
|
@ -268,20 +240,12 @@ public abstract class AbstractMonitor<O extends KubernetesObject,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an observed change.
|
* Handle an observed change. The method is invoked by the observer
|
||||||
|
* thread(s). It is the responsibility of the implementing class to
|
||||||
|
* fire derived events on the appropriate event pipeline.
|
||||||
*
|
*
|
||||||
* @param client the client
|
* @param client the client
|
||||||
* @param change the change
|
* @param change the change
|
||||||
*/
|
*/
|
||||||
protected abstract void handleChange(K8sClient client, Response<O> change);
|
protected abstract void handleChange(K8sClient client, Response<O> change);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link Channel} for the given name.
|
|
||||||
*
|
|
||||||
* @param name the name
|
|
||||||
* @return the channel used for events related to the specified object
|
|
||||||
*/
|
|
||||||
protected Optional<C> channel(String name) {
|
|
||||||
return channelManager.getChannel(name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,18 @@
|
||||||
|
|
||||||
package org.jdrupes.vmoperator.manager;
|
package org.jdrupes.vmoperator.manager;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import freemarker.template.AdapterTemplateModel;
|
||||||
import freemarker.template.Configuration;
|
import freemarker.template.Configuration;
|
||||||
import freemarker.template.TemplateException;
|
import freemarker.template.TemplateException;
|
||||||
|
import freemarker.template.TemplateMethodModelEx;
|
||||||
|
import freemarker.template.TemplateModel;
|
||||||
|
import freemarker.template.TemplateModelException;
|
||||||
|
import freemarker.template.utility.DeepUnwrap;
|
||||||
import io.kubernetes.client.custom.V1Patch;
|
import io.kubernetes.client.custom.V1Patch;
|
||||||
import io.kubernetes.client.openapi.ApiClient;
|
import io.kubernetes.client.openapi.ApiClient;
|
||||||
import io.kubernetes.client.openapi.ApiException;
|
import io.kubernetes.client.openapi.ApiException;
|
||||||
|
import io.kubernetes.client.openapi.models.V1ObjectMeta;
|
||||||
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
|
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesApi;
|
||||||
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
|
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
|
||||||
import io.kubernetes.client.util.generic.dynamic.Dynamics;
|
import io.kubernetes.client.util.generic.dynamic.Dynamics;
|
||||||
|
|
@ -30,13 +37,18 @@ import io.kubernetes.client.util.generic.options.ListOptions;
|
||||||
import io.kubernetes.client.util.generic.options.PatchOptions;
|
import io.kubernetes.client.util.generic.options.PatchOptions;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import org.jdrupes.vmoperator.common.K8s;
|
import org.jdrupes.vmoperator.common.K8s;
|
||||||
import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
|
import static org.jdrupes.vmoperator.manager.Constants.APP_NAME;
|
||||||
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
|
import static org.jdrupes.vmoperator.manager.Constants.VM_OP_NAME;
|
||||||
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
import org.jdrupes.vmoperator.manager.events.VmChannel;
|
||||||
import org.jdrupes.vmoperator.manager.events.VmDefChanged;
|
import org.jdrupes.vmoperator.util.DataPath;
|
||||||
|
import org.jdrupes.vmoperator.util.GsonPtr;
|
||||||
import org.yaml.snakeyaml.LoaderOptions;
|
import org.yaml.snakeyaml.LoaderOptions;
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||||
|
|
@ -44,7 +56,6 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||||
/**
|
/**
|
||||||
* Delegee for reconciling the config map
|
* Delegee for reconciling the config map
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataflowAnomalyAnalysis")
|
|
||||||
/* default */ class ConfigMapReconciler {
|
/* default */ class ConfigMapReconciler {
|
||||||
|
|
||||||
protected final Logger logger = Logger.getLogger(getClass().getName());
|
protected final Logger logger = Logger.getLogger(getClass().getName());
|
||||||
|
|
@ -62,34 +73,72 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||||
/**
|
/**
|
||||||
* Reconcile.
|
* Reconcile.
|
||||||
*
|
*
|
||||||
* @param event the event
|
|
||||||
* @param model the model
|
* @param model the model
|
||||||
* @param channel the channel
|
* @param channel the channel
|
||||||
* @return the dynamic kubernetes object
|
* @param modelChanged the model has changed
|
||||||
* @throws IOException Signals that an I/O exception has occurred.
|
* @throws IOException Signals that an I/O exception has occurred.
|
||||||
* @throws TemplateException the template exception
|
* @throws TemplateException the template exception
|
||||||
* @throws ApiException the api exception
|
* @throws ApiException the API exception
|
||||||
*/
|
*/
|
||||||
public DynamicKubernetesObject reconcile(VmDefChanged event,
|
public void reconcile(Map<String, Object> model, VmChannel channel,
|
||||||
Map<String, Object> model, VmChannel channel)
|
boolean modelChanged)
|
||||||
throws IOException, TemplateException, ApiException {
|
throws IOException, TemplateException, ApiException {
|
||||||
// Get API
|
// Check if an update is needed
|
||||||
DynamicKubernetesApi cmApi = new DynamicKubernetesApi("", "v1",
|
var prevData = channel.associated(PrevData.class)
|
||||||
"configmaps", channel.client());
|
.orElseGet(() -> new PrevData(null, new HashMap<>()));
|
||||||
|
Object newInputs = model.get("loginRequestedFor");
|
||||||
|
if (!modelChanged && Objects.equals(prevData.inputs, newInputs)) {
|
||||||
|
// Make added data available in new model
|
||||||
|
model.putAll(prevData.added);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prevData = new PrevData(newInputs, prevData.added);
|
||||||
|
channel.setAssociated(PrevData.class, prevData);
|
||||||
|
|
||||||
// Combine template and data and parse result
|
// Combine template and data and parse result
|
||||||
|
logger.fine(() -> "Create/update configmap "
|
||||||
|
+ DataPath.<String> get(model, "cr", "name").orElse("unknown"));
|
||||||
|
model.put("adjustCloudInitMeta", adjustCloudInitMetaModel);
|
||||||
|
prevData.added.put("adjustCloudInitMeta", adjustCloudInitMetaModel);
|
||||||
var fmTemplate = fmConfig.getTemplate("runnerConfig.ftl.yaml");
|
var fmTemplate = fmConfig.getTemplate("runnerConfig.ftl.yaml");
|
||||||
StringWriter out = new StringWriter();
|
StringWriter out = new StringWriter();
|
||||||
fmTemplate.process(model, out);
|
fmTemplate.process(model, out);
|
||||||
// Avoid Yaml.load due to
|
// Avoid Yaml.load due to
|
||||||
// https://github.com/kubernetes-client/java/issues/2741
|
// https://github.com/kubernetes-client/java/issues/2741
|
||||||
var mapDef = Dynamics.newFromYaml(
|
var newCm = Dynamics.newFromYaml(
|
||||||
new Yaml(new SafeConstructor(new LoaderOptions())), out.toString());
|
new Yaml(new SafeConstructor(new LoaderOptions())), out.toString());
|
||||||
|
|
||||||
|
// Maybe override logging.properties from reconciler configuration.
|
||||||
|
DataPath.<String> get(model, "reconciler", "loggingProperties")
|
||||||
|
.ifPresent(props -> {
|
||||||
|
GsonPtr.to(newCm.getRaw()).getAs(JsonObject.class, "data")
|
||||||
|
.get().addProperty("logging.properties", props);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Maybe override logging.properties from VM definition.
|
||||||
|
DataPath.<String> get(model, "cr", "spec", "loggingProperties")
|
||||||
|
.ifPresent(props -> {
|
||||||
|
GsonPtr.to(newCm.getRaw()).getAs(JsonObject.class, "data")
|
||||||
|
.get().addProperty("logging.properties", props);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get API and update
|
||||||
|
DynamicKubernetesApi cmApi = new DynamicKubernetesApi("", "v1",
|
||||||
|
"configmaps", channel.client());
|
||||||
|
|
||||||
// Apply and maybe force pod update
|
// Apply and maybe force pod update
|
||||||
var newState = K8s.apply(cmApi, mapDef, out.toString());
|
var updatedCm = K8s.apply(cmApi, newCm, newCm.getRaw().toString());
|
||||||
maybeForceUpdate(channel.client(), newState);
|
maybeForceUpdate(channel.client(), updatedCm);
|
||||||
return newState;
|
model.put("configMapResourceVersion",
|
||||||
|
updatedCm.getMetadata().getResourceVersion());
|
||||||
|
prevData.added.put("configMapResourceVersion",
|
||||||
|
updatedCm.getMetadata().getResourceVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key for association.
|
||||||
|
*/
|
||||||
|
private record PrevData(Object inputs, Map<String, Object> added) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -135,4 +184,27 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final TemplateMethodModelEx adjustCloudInitMetaModel
|
||||||
|
= new TemplateMethodModelEx() {
|
||||||
|
@Override
|
||||||
|
public Object exec(@SuppressWarnings("rawtypes") List arguments)
|
||||||
|
throws TemplateModelException {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
var res = new HashMap<>((Map<String, Object>) DeepUnwrap
|
||||||
|
.unwrap((TemplateModel) arguments.get(0)));
|
||||||
|
var metadata
|
||||||
|
= (V1ObjectMeta) ((AdapterTemplateModel) arguments.get(1))
|
||||||
|
.getAdaptedObject(Object.class);
|
||||||
|
if (!res.containsKey("instance-id")) {
|
||||||
|
res.put("instance-id",
|
||||||
|
Optional.ofNullable(metadata.getGeneration())
|
||||||
|
.map(s -> "v" + s).orElse("v1"));
|
||||||
|
}
|
||||||
|
if (!res.containsKey("local-hostname")) {
|
||||||
|
res.put("local-hostname", metadata.getName());
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,18 +21,8 @@ package org.jdrupes.vmoperator.manager;
|
||||||
/**
|
/**
|
||||||
* Some constants.
|
* Some constants.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("PMD.DataClass")
|
|
||||||
public class Constants extends org.jdrupes.vmoperator.common.Constants {
|
public class Constants extends org.jdrupes.vmoperator.common.Constants {
|
||||||
|
|
||||||
/** The Constant COMP_DISPLAY_SECRET. */
|
|
||||||
public static final String COMP_DISPLAY_SECRET = "display-secret";
|
|
||||||
|
|
||||||
/** The Constant DATA_DISPLAY_PASSWORD. */
|
|
||||||
public static final String DATA_DISPLAY_PASSWORD = "display-password";
|
|
||||||
|
|
||||||
/** The Constant DATA_PASSWORD_EXPIRY. */
|
|
||||||
public static final String DATA_PASSWORD_EXPIRY = "password-expiry";
|
|
||||||
|
|
||||||
/** The Constant STATE_RUNNING. */
|
/** The Constant STATE_RUNNING. */
|
||||||
public static final String STATE_RUNNING = "Running";
|
public static final String STATE_RUNNING = "Running";
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue