Compare commits

..

No commits in common. "main" and "gh-pages" have entirely different histories.

407 changed files with 0 additions and 52180 deletions

View file

@ -1,7 +0,0 @@
---
exclude_paths:
- 'buildSrc/**'
- '*/test/**'
- '*/test-resources/**'
- 'misc/stylesheet.css'

View file

@ -1,14 +0,0 @@
root = true
[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
[*.{html,md,yml,yaml}]
indent_size = 2
indent_style = space
[*.{gradle,js,ts}]
indent_size = 4
indent_style = space

View file

@ -1,16 +0,0 @@
{
"root": true,
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_"
}
]
},
"ignorePatterns": ["src/**/*.test.ts", "build/**/*"]
}

9
.gitattributes vendored
View file

@ -1,9 +0,0 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf
# These are Windows script files and should use crlf
*.bat text eol=crlf

View file

@ -1,66 +0,0 @@
name: "CodeQL"
on:
push:
branches: [ "*" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
schedule:
- cron: '20 3 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'java' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ]
# Use only 'java' to analyze code written in Java, Kotlin or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View file

@ -1,31 +0,0 @@
name: Java CI with Gradle
on:
push: {}
pull_request: {}
permissions:
contents: read
concurrency:
group: doc_generation
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- 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 with Gradle
run: ./gradlew -Pwebsite.push.token=${{ secrets.WEBSITE_PUSH_TOKEN }} stage

View file

@ -1,89 +0,0 @@
# 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

View file

@ -1,40 +0,0 @@
name: Push Container Image with Gradle
on:
release:
types: [published]
permissions:
contents: read
packages: write
concurrency:
group: doc_generation
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install graphviz
run: sudo apt-get install graphviz
- name: Install podman
run: sudo apt-get install podman
- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
- name: Push with Gradle
run: ./gradlew -Pwebsite.push.token=${{ secrets.WEBSITE_PUSH_TOKEN }} -Pdocker.registry=ghcr.io/${{ github.actor }} stage publishImage

15
.gitignore vendored
View file

@ -1,15 +0,0 @@
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
build
# Eclipse
bin
.classpath
.project
org.eclipse.jdt.core.prefs
**/.externalToolBuilders/
# Node
**/node_modules/

View file

@ -1,30 +0,0 @@
# 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

View file

@ -1,13 +0,0 @@
arguments=--init-script /home/mnl/.config/Code/User/globalStorage/redhat.java/1.24.0/config_linux/org.eclipse.osgi/55/0/.cp/gradle/init/init.gradle --init-script /home/mnl/.config/Code/User/globalStorage/redhat.java/1.24.0/config_linux/org.eclipse.osgi/55/0/.cp/gradle/protobuf/init.gradle
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
gradle.user.home=
java.home=
jvm.arguments=
offline.mode=false
override.workspace.settings=true
show.console.view=true
show.executions.view=true

View file

@ -1,2 +0,0 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8

View file

@ -1,2 +0,0 @@
eclipse.preferences.version=1
line.separator=\n

30
.vscode/launch.json vendored
View file

@ -1,30 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Current File",
"request": "launch",
"mainClass": "${file}"
},
{
"type": "java",
"name": "Manager",
"request": "launch",
"mainClass": "org.jdrupes.vmoperator.manager.Manager",
"projectName": "org.jdrupes.vmoperator.manager"
},
{
"type": "java",
"name": "Runner",
"request": "launch",
"mainClass": "org.jdrupes.vmoperator.runner.qemu.Runner",
"projectName": "org.jdrupes.vmoperator.runner.qemu",
"cwd": "${workspaceFolder}/org.jdrupes.vmoperator.runner.qemu",
"vmArgs": "-ea -Djava.util.logging.manager=org.jdrupes.vmoperator.util.LongLoggingManager"
}
]
}

View file

@ -1,5 +0,0 @@
{
"java.compile.nullAnalysis.mode": "disabled",
"java.configuration.updateBuildConfiguration": "interactive",
"java.dependency.packagePresentation": "flat"
}

View file

@ -1,38 +0,0 @@
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

View file

@ -1,25 +0,0 @@
[![Java CI with Gradle](https://github.com/mnlipp/VM-Operator/actions/workflows/gradle.yml/badge.svg)](https://github.com/mnlipp/VM-Operator/actions/workflows/gradle.yml)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/2277842dac894de4b663c6aa2779077e)](https://app.codacy.com/gh/mnlipp/VM-Operator/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
![Latest Manager](https://img.shields.io/github/v/tag/mnlipp/vm-operator?filter=manager*&label=latest)
![Latest Runner](https://img.shields.io/github/v/tag/mnlipp/vm-operator?filter=runner-qemu*&label=latest)
# Run QEMU/KVM in Kubernetes Pods
![Overview picture](webpages/index-pic.svg)
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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View file

@ -1,39 +0,0 @@
buildscript {
repositories {
gradlePluginPortal()
}
}
plugins {
id 'org.ajoberstar.grgit' version '5.2.0'
id 'org.ajoberstar.git-publish' version '4.2.0' apply false
id 'pl.allegro.tech.build.axion-release' version '1.17.2' apply false
id 'org.jdrupes.vmoperator.versioning-conventions'
id 'org.jdrupes.vmoperator.java-doc-conventions'
id 'eclipse'
id "com.github.node-gradle.node" version "7.0.1"
}
allprojects {
project.group = 'org.jdrupes.vmoperator'
}
task stage {
description = 'To be executed by CI.'
group = 'build'
// Build everything first
gradle.projectsEvaluated {
dependsOn subprojects.tasks.collect {
tc -> tc.findByName("build") }.flatten()
}
}
eclipse {
project {
file {
project.natures += 'org.eclipse.buildship.core.gradleprojectnature'
project.buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder'
}
}
}

View file

@ -1,13 +0,0 @@
arguments=
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
gradle.user.home=
java.home=
jvm.arguments=
offline.mode=false
override.workspace.settings=false
show.console.view=true
show.executions.view=true

View file

@ -1,2 +0,0 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8

View file

@ -1,2 +0,0 @@
eclipse.preferences.version=1
line.separator=\n

View file

@ -1,13 +0,0 @@
#
#Wed Oct 02 14:48:43 CEST 2024
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=21
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=21

View file

@ -1,3 +0,0 @@
eclipse.preferences.version=1
groovy.compiler.level=-1
groovy.script.filters=**/*.dsld,y,**/*.gradle,n

View file

@ -1,34 +0,0 @@
plugins {
// Support convention plugins written in Groovy. Convention plugins
// are build scripts in 'src/main' that automatically become available
// as plugins in the main build.
id 'groovy-gradle-plugin'
// Apply eclipse plugin
id 'eclipse'
}
sourceSets {
main {
groovy {
srcDirs = ['src']
}
resources {
srcDirs = ['resources']
}
}
}
eclipse {
jdt {
file {
withProperties { properties ->
def formatterPrefs = new Properties()
rootProject.file("../gradle/org.eclipse.jdt.core.formatter.prefs")
.withInputStream { formatterPrefs.load(it) }
properties.putAll(formatterPrefs)
}
}
}
}

View file

@ -1,13 +0,0 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This project uses @Incubating APIs which are subject to change.
*/
plugins {
// Apply the common convention plugin for shared build configuration between library and application projects.
id 'org.jdrupes.vmoperator.java-common-conventions'
// Apply the application plugin to add support for building a CLI application in Java.
id 'application'
}

View file

@ -1,136 +0,0 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This project uses @Incubating APIs which are subject to change.
*/
plugins {
// Apply the common versioning conventions.
// Put this at the start, because accessing project.version before
// this is applied makes things fail.
id 'org.jdrupes.vmoperator.versioning-conventions'
// Apply the java Plugin to add support for Java.
id 'java'
// Apply eclipse plugin
id 'eclipse'
// Access to git information
id 'org.ajoberstar.grgit'
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
dependencies {
constraints {
// Define dependency versions as constraints
// implementation 'org.apache.commons:commons-text:1.9'
}
}
// Configure sensible layout
sourceSets {
main {
java {
srcDirs = ['src']
}
resources {
srcDirs = ['resources']
}
}
test {
java {
srcDirs = ['test']
}
resources {
srcDirs = ['test-resources']
}
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
jar {
manifest {
def matchExpr = [ project.tagName + "*" ]
inputs.property("gitDescriptor",
{ grgit.describe(always: true, match: matchExpr) })
// Set Git revision information in the manifests of built bundles
def gitDesc = grgit.describe(always: true, match: matchExpr)
attributes([
"Implementation-Title": project.name,
"Implementation-Version": "$project.version (built from ${gitDesc})",
"Implementation-Vendor": grgit.repository.jgit.repository.config.getString("user", null, "name")
+ " (" + grgit.repository.jgit.repository.config.getString("user", null, "email") + ")",
"Git-Descriptor": gitDesc,
"Git-SHA": grgit.head().id,
])
}
}
eclipse {
project {
file {
// closure executed after .project content is loaded from existing file
// and before gradle build information is merged
beforeMerged { project ->
project.natures.clear()
project.buildCommands.clear()
}
project.natures += 'org.eclipse.buildship.core.gradleprojectnature'
project.buildCommand 'org.eclipse.buildship.core.gradleprojectbuilder'
if (plugins.hasPlugin('java')) {
project.buildCommand 'org.eclipse.jdt.core.javabuilder'
project.natures += 'net.sf.eclipsecs.core.CheckstyleNature'
project.buildCommand 'net.sf.eclipsecs.core.CheckstyleBuilder'
project.natures += 'ch.acanda.eclipse.pmd.builder.PMDNature'
project.buildCommand 'ch.acanda.eclipse.pmd.builder.PMDBuilder'
}
if (plugins.hasPlugin('biz.aQute.bnd')) {
project.natures += 'bndtools.core.bndnature'
project.buildCommand 'bndtools.core.bndbuilder'
}
}
}
classpath {
downloadJavadoc = true
downloadSources = true
}
jdt {
file {
withProperties { properties ->
def formatterPrefs = new Properties()
rootProject.file("gradle/org.eclipse.jdt.core.formatter.prefs")
.withInputStream { formatterPrefs.load(it) }
properties.putAll(formatterPrefs)
}
}
}
}
testing {
suites {
// Configure the built-in test suite
test {
// Use JUnit Jupiter test framework
useJUnitJupiter('5.9.1')
}
}
}

View file

@ -1,120 +0,0 @@
plugins {
// Apply the common versioning conventions.
id 'org.jdrupes.vmoperator.versioning-conventions'
id 'org.ajoberstar.git-publish'
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
var docDestinationDir = file("${rootProject.buildDir}/javadoc")
configurations {
javadocTaglets
}
configurations {
markdownDoclet
javadocTaglets
}
dependencies {
markdownDoclet "org.jdrupes.mdoclet:doclet:4.0.0"
javadocTaglets "org.jdrupes.taglets:plantuml-taglet:3.0.0"
}
task apidocs (type: JavaExec) {
// Does not work on JitPack, no /usr/bin/dot
enabled = JavaVersion.current() == JavaVersion.VERSION_21
outputs.dir(docDestinationDir)
inputs.file rootProject.file('overview.md')
inputs.file "${rootProject.rootDir}/misc/javadoc-overwrites.css"
jvmArgs = ['--add-exports=jdk.compiler/com.sun.tools.doclint=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
'--add-exports=jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
'--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit=ALL-UNNAMED',
'--add-opens=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit.resources.releases=ALL-UNNAMED',
'-Duser.language=en', '-Duser.region=US']
mainClass = 'jdk.javadoc.internal.tool.Main'
gradle.projectsEvaluated {
// Make sure that other projects' compileClasspaths are resolved
rootProject.subprojects
.findAll({project -> project.name.startsWith("org.jdrupes")})
.each{project -> dependsOn project.tasks.getByName('assemble')}
classpath = files(rootProject.subprojects
.findAll({project -> project.name.startsWith("org.jdrupes")})
.collect {project -> project.sourceSets.main.compileClasspath})
var sourceDirs = rootProject.subprojects
.findAll({project -> project.name.startsWith("org.jdrupes")})
.collect{project -> project.sourceSets.main.allJava.getSrcDirs() }
.flatten().findAll(dir -> dir.exists())
sourceDirs.each { inputs.dir(it) }
args = ['-doctitle', """VM-Operator
(runner.qemu-${-> findProject(':org.jdrupes.vmoperator.runner.qemu').version},
manager-${-> findProject(':org.jdrupes.vmoperator.manager').version})""",
'-package',
'-use',
'-linksource',
'-link', 'https://docs.oracle.com/en/java/javase/21/docs/api/',
'-link', 'https://jgrapes.org/latest-release/javadoc/',
'-link', 'https://freemarker.apache.org/docs/api/',
'--add-exports', 'jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
'--add-exports', 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
'-doclet', 'org.jdrupes.mdoclet.MDoclet',
'-docletpath', configurations.markdownDoclet.files.asType(List).join(":"),
'--disable-auto-highlight',
'-tagletpath', configurations.javadocTaglets.files.asType(List).join(":"),
'-taglet', 'org.jdrupes.taglets.plantUml.PlantUml',
'-taglet', 'org.jdrupes.taglets.plantUml.StartUml',
'-taglet', 'org.jdrupes.taglets.plantUml.EndUml',
'-overview', "${rootProject.file('overview.md')}",
'-d', docDestinationDir,
'-sourcepath', sourceDirs.join(':'),
'-subpackages', 'org.jdrupes',
'-bottom', rootProject.file("misc/javadoc.bottom.txt").text,
'--allow-script-in-comments',
'-Xdoclint:-html',
'--add-stylesheet', "${rootProject.rootDir}/misc/javadoc-overwrites.css",
'--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.formats.html=ALL-UNNAMED',
'-quiet'
]
}
ignoreExitValue true
}
task testJavadoc(type: Javadoc) {
enabled = JavaVersion.current() == JavaVersion.VERSION_21
source = fileTree(dir: 'testfiles', include: '**/*.java')
destinationDir = project.file("build/testfiles-gradle")
options.docletpath = configurations.markdownDoclet.files.asType(List)
options.doclet = 'org.jdrupes.mdoclet.MDoclet'
options.overview = 'testfiles/overview.md'
options.addStringOption('Xdoclint:-html', '-quiet')
options.setJFlags([
'--add-exports=jdk.compiler/com.sun.tools.doclint=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
'--add-exports=jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
'--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit=ALL-UNNAMED',
'--add-opens=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit.resources.releases=ALL-UNNAMED'])
}
// Prepare github authentication for plugins
if (System.properties['org.ajoberstar.grgit.auth.username'] == null) {
System.setProperty('org.ajoberstar.grgit.auth.username',
project.rootProject.properties['website.push.token'] ?: "nouser")
}

View file

@ -1,13 +0,0 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This project uses @Incubating APIs which are subject to change.
*/
plugins {
// Apply the common convention plugin for shared build configuration between library and application projects.
id 'org.jdrupes.vmoperator.java-common-conventions'
// Apply the java-library plugin for API and implementation separation.
id 'java-library'
}

View file

@ -1,36 +0,0 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This project uses @Incubating APIs which are subject to change.
*/
plugins {
// Required by axion-release
id 'org.ajoberstar.grgit'
// Git based versioning
id 'pl.allegro.tech.build.axion-release'
}
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 {
versionIncrementer 'incrementMinor'
tag {
prefix = project.tagName
}
}
project.version = scmVersion.version
ext.isSnapshot = version.endsWith('-SNAPSHOT')

View file

@ -1,149 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
JGrapes Event Driven Framework
Copyright (C) 2016, 2018 Michael N. Lipp
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see <http://www.gnu.org/licenses/>.
-->
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<!--
This configuration file was written by the eclipse-cs plugin configuration editor
-->
<!--
Checkstyle-Configuration: Project Checks
Description: none
-->
<module name="Checker">
<property name="severity" value="warning"/>
<property name="charset" value="UTF-8"/>
<property name="fileExtensions" value="java, properties, xml"/>
<module name="SuppressWarningsFilter"/>
<module name="TreeWalker">
<property name="tabWidth" value="4"/>
<module name="SuppressWarningsHolder"/>
<module name="OuterTypeFilename"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
<property name="format" value="\\u00(08|09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message" value="Avoid using corresponding octal or Unicode escape."/>
</module>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="OneTopLevelClass"/>
<module name="NoLineWrap"/>
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="NeedBraces"/>
<module name="RightCurly">
<property name="option" value="alone"/>
<property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT, INSTANCE_INIT"/>
</module>
<module name="OneStatementPerLine"/>
<module name="MultipleVariableDeclarations"/>
<module name="ArrayTypeStyle"/>
<module name="FallThrough"/>
<module name="UpperEll"/>
<module name="ModifierOrder"/>
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="SeparatorWrap">
<property name="option" value="nl"/>
<property name="tokens" value="DOT"/>
</module>
<module name="SeparatorWrap">
<property name="option" value="EOL"/>
<property name="tokens" value="COMMA"/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern" value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="TypeName">
<message key="name.invalidPattern" value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MemberName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern" value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern" value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalVariableName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<property name="allowOneCharVarInForLoop" value="true"/>
<property name="tokens" value="VARIABLE_DEF"/>
<message key="name.invalidPattern" value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ClassTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern" value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern" value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="NoFinalizer"/>
<module name="AbbreviationAsWordInName">
<property name="allowedAbbreviationLength" value="1"/>
<property name="allowedAbbreviations" value="IO,MX,KV,DTO"/>
<property name="ignoreFinal" value="false"/>
</module>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<module name="CustomImportOrder">
<property name="sortImportsInGroupAlphabetically" value="true"/>
</module>
<module name="MethodParamPad"/>
<module name="OperatorWrap">
<property name="option" value="NL"/>
<property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR "/>
</module>
<module name="AnnotationLocation">
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
</module>
<module name="AnnotationLocation">
<property name="tokens" value="VARIABLE_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
<message key="name.invalidPattern" value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected"/>
</module>
<module name="LocalVariableName">
<property name="allowOneCharVarInForLoop" value="true"/>
</module>
<module name="AvoidStarImport">
<property name="allowStaticMemberImports" value="true"/>
</module>
<module name="DefaultComesLast"/>
<module name="EqualsHashCode"/>
</module>
<module name="LineLength">
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
<property name="max" value="100"/>
</module>
</module>

View file

@ -1,74 +0,0 @@
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

File diff suppressed because it is too large Load diff

View file

@ -1,14 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- vmop-role.yaml
- vmop-service-account.yaml
- vmop-role-binding.yaml
- vmop-image-repository-pvc.yaml
- vmop-config-map.yaml
- vmop-deployment.yaml
- vmop-service.yaml
- vmrunner-role.yaml
- vmrunner-service-account.yaml
- vmrunner-role-binding.yaml

View file

@ -1,24 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: vm-operator
labels:
app.kubernetes.io/name: vm-operator
data:
config.yaml: |
"/Manager": {}
logging.properties: |
handlers=java.util.logging.ConsoleHandler, \
org.jgrapes.webconlet.logviewer.LogViewerHandler
org.jgrapes.level=FINE
org.jdrupes.vmoperator.manager.level=FINE
java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=%1$tb %1$td %1$tT %4$s %5$s%6$s%n
org.jgrapes.webconlet.logviewer.LogViewerHandler.level=CONFIG

View file

@ -1,61 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: vm-operator
labels:
app.kubernetes.io/name: vm-operator
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: vm-operator
app.kubernetes.io/component: manager
template:
metadata:
name: vm-operator
labels:
app.kubernetes.io/name: vm-operator
app.kubernetes.io/component: manager
spec:
containers:
- name: vm-operator
image: >-
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:
- name: config
mountPath: /etc/opt/vmoperator
- name: vmop-image-repository
mountPath: /var/local/vmop-image-repository
securityContext:
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
volumes:
- name: config
configMap:
name: vm-operator
- name: vmop-image-repository
persistentVolumeClaim:
claimName: vmop-image-repository
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: vm-operator
securityContext:
runAsUser: 65534
runAsNonRoot: true

View file

@ -1,13 +0,0 @@
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: vmop-image-repository
labels:
app.kubernetes.io/name: vm-operator
spec:
accessModes:
- ReadOnlyMany
resources:
requests:
storage: 100Gi
volumeMode: Filesystem

View file

@ -1,13 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: vm-operator
labels:
app.kubernetes.io/name: vm-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: vm-operator
subjects:
- kind: ServiceAccount
name: vm-operator

View file

@ -1,46 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: vm-operator
labels:
app.kubernetes.io/name: vm-operator
rules:
- apiGroups:
- vmoperator.jdrupes.org
resources:
- vms
- vmpools
verbs:
- '*'
- apiGroups:
- vmoperator.jdrupes.org
resources:
- vms/status
verbs:
- patch
- apiGroups:
- apps
resources:
- statefulsets
verbs:
- '*'
- apiGroups:
- ""
resources:
- configmaps
- secrets
- services
verbs:
- '*'
- apiGroups:
- ""
resources:
- persistentvolumeclaims
- pods
verbs:
- watch
- list
- get
- create
- delete
- patch

View file

@ -1,6 +0,0 @@
kind: ServiceAccount
apiVersion: v1
metadata:
name: vm-operator
labels:
app.kubernetes.io/name: vm-operator

View file

@ -1,12 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: vm-operator
spec:
ports:
- port: 8080
protocol: TCP
targetPort: 8080
selector:
app.kubernetes.io/name: vm-operator
app.kubernetes.io/component: manager

View file

@ -1,13 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: vm-runner
labels:
app.kubernetes.io/name: vm-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: vm-runner
subjects:
- kind: ServiceAccount
name: vm-runner

View file

@ -1,27 +0,0 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: vm-runner
labels:
app.kubernetes.io/name: vm-operator
rules:
- apiGroups:
- vmoperator.jdrupes.org
resources:
- vms
verbs:
- list
- get
- patch
- apiGroups:
- vmoperator.jdrupes.org
resources:
- vms/status
verbs:
- patch
- apiGroups:
- events.k8s.io
resources:
- events
verbs:
- create

View file

@ -1,6 +0,0 @@
kind: ServiceAccount
apiVersion: v1
metadata:
name: vm-runner
labels:
app.kubernetes.io/name: vm-operator

View file

@ -1,4 +0,0 @@
/test-vm-ci.yaml
/kubeconfig.yaml
/crds/
/.vm-operator-cmd.rc

View file

@ -1,19 +0,0 @@
# Example setup for development
The CRD must be deployed independently. Apart from that, the
`kustomize.yaml`
* creates a small cdrom image repository and
* deploys the operator in namespace `vmop-dev` with a replica of 0.
This allows you to run the manager in your IDE.
The `kustomize.yaml` also changes the container image repository for
the operator to a private repository for development. You have to
adapt this to your own repository if you also want to test your
development version in a container.
If you want to run the unittests, this setup *must* be used with a private
container image repository which must match the one configured
for gradle pushImages.

View file

@ -1,90 +0,0 @@
# Used for running manager outside Kubernetes.
# Keep in sync with kustomize.yaml
"/Manager":
# If provided, is shown at top left before namespace
# clusterName: "test"
# The controller manages the VM
"/Controller":
namespace: vmop-dev
"/Reconciler":
runnerDataPvc:
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":
port: 8888
"/GuiHttpServer":
# This configures the GUI
"/ConsoleWeblet":
"/WebConsole":
"/LoginConlet":
users:
- name: admin
fullName: Administrator
password: "$2b$05$NiBd74ZGdplLC63ePZf1f.UtjMKkbQ23cQoO2OKOFalDBHWAOy21."
- name: operator
fullName: Operator
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":
rolesByUser:
# User admin has role admin
admin:
- admin
operator:
- operator
test1:
- user
test2:
- user
test3:
- user
# All users have role other
"*":
- other
replace: false
"/RoleConletFilter":
conletTypesByRole:
# Admins can use all conlets
admin:
- "*"
operator:
- org.jdrupes.vmoperator.vmmgmt.VmMgmt
- org.jdrupes.vmoperator.vmaccess.VmAccess
user:
- org.jdrupes.vmoperator.vmaccess.VmAccess
# Others cannot use any conlet (except login conlet to log out)
other:
- org.jgrapes.webconlet.oidclogin.LoginConlet
"/ComponentCollector":
"/VmAccess":
displayResource:
preferredIpVersion: ipv4
syncPreviewsFor:
- role: user

View file

@ -1,47 +0,0 @@
#!/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

View file

@ -1,112 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../deploy
namespace: vmop-dev
patches:
- patch: |-
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: vmop-image-repository
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: local-path
- patch: |-
kind: ConfigMap
apiVersion: v1
metadata:
name: vm-operator
data:
# Keep in sync with config.yaml
config.yaml: |
"/Manager":
# clusterName: "test"
"/Controller":
namespace: vmop-dev
"/Reconciler":
runnerData:
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":
port: 8888
"/GuiHttpServer":
# This configures the GUI
"/ConsoleWeblet":
"/WebConsole":
"/LoginConlet":
users:
- name: admin
fullName: Administrator
password: "$2b$05$NiBd74ZGdplLC63ePZf1f.UtjMKkbQ23cQoO2OKOFalDBHWAOy21."
- name: test1
fullName: Test Account
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":
rolesByUser:
# User admin has role admin
admin:
- admin
test1:
- user
test2:
- user
test3:
- user
# All users have role other
"*":
- other
replace: false
"/RoleConletFilter":
conletTypesByRole:
# Admins can use all conlets
admin:
- "*"
user:
- org.jdrupes.vmoperator.vmviewer.VmViewer
# Others cannot use any conlet (except login conlet to log out)
other:
- org.jgrapes.webconlet.locallogin.LoginConlet
"/ComponentCollector":
"/VmAccess":
displayResource:
preferredIpVersion: ipv4
syncPreviewsFor:
- role: user
- target:
group: apps
version: v1
kind: Deployment
name: vm-operator
patch: |-
- op: replace
path: /spec/template/spec/containers/0/image
value: docker-registry.lan.mnl.de/vmoperator/org.jdrupes.vmoperator.manager:test
- op: replace
path: /spec/template/spec/containers/0/imagePullPolicy
value: Always
- op: replace
path: /spec/replicas
value: 0

View file

@ -1,66 +0,0 @@
#!/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

View file

@ -1,17 +0,0 @@
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

View file

@ -1,13 +0,0 @@
kind: Secret
apiVersion: v1
metadata:
name: test-vm-display-secret
namespace: vmop-dev
labels:
app.kubernetes.io/name: vm-runner
app.kubernetes.io/instance: test-vm
app.kubernetes.io/component: display-secret
type: Opaque
data:
display-password: dGVzdC12bQ==
password-expiry: KzMw

View file

@ -1,28 +0,0 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: test-vm-system
labels:
app.kubernetes.io/name: vmrunner
app.kubernetes.io/instance: test-vm
vmrunner.jdrupes.org/disk: system
spec:
capacity:
storage: 40Gi
volumeMode: Block
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
claimRef:
name: system-disk-test-vm-0
namespace: qemu-vms
local:
path: /dev/vgmain/test-vm
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- olymp

View file

@ -1,30 +0,0 @@
kind: Pod
apiVersion: v1
metadata:
name: test-vm-shell
namespace: vmop-dev
spec:
volumes:
- name: test-vm-system-disk
persistentVolumeClaim:
claimName: system-disk-test-vm-0
- name: vmop-image-repository
persistentVolumeClaim:
claimName: vmop-image-repository
containers:
- name: test-vm-shell
image: archlinux/archlinux
args:
- bash
imagePullPolicy: Always
stdin: true
stdinOnce: true
tty: true
volumeDevices:
- name: test-vm-system-disk
devicePath: /dev/test-vm-system-disk
volumeMounts:
- name: vmop-image-repository
mountPath: /var/local/vmop-image-repository
securityContext:
privileged: true

View file

@ -1,10 +0,0 @@
---
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

View file

@ -1,66 +0,0 @@
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)) %>

View file

@ -1,62 +0,0 @@
apiVersion: "vmoperator.jdrupes.org/v1"
kind: VirtualMachine
metadata:
namespace: vmop-dev
name: test-vm
spec:
image:
source: registry.mnl.de/org/jdrupes/vm-operator/org.jdrupes.vmoperator.runner.qemu-arch:testing
pullPolicy: Always
permissions:
- user: admin
may:
- "*"
resources:
requests:
cpu: 1
memory: 2Gi
guestShutdownStops: true
cloudInit: {}
vm:
# state: Running
bootMenu: yes
useTpm: true
maximumRam: 8Gi
currentRam: 4Gi
maximumCpus: 4
currentCpus: 4
networks:
# No bridge on test cluster
- user: {}
disks:
- volumeClaimTemplate:
metadata:
name: system
spec:
storageClassName: ""
selector:
matchLabels:
app.kubernetes.io/name: vmrunner
app.kubernetes.io/instance: test-vm
vmrunner.jdrupes.org/disk: system
resources:
requests:
storage: 40Gi
- cdrom:
image: ""
# image: https://download.fedoraproject.org/pub/fedora/linux/releases/38/Workstation/x86_64/iso/Fedora-Workstation-Live-x86_64-38-1.6.iso
# image: "Fedora-Workstation-Live-x86_64-38-1.6.iso"
display:
spice:
port: 5810
generateSecret: true
loadBalancerService: {}

View file

@ -1,2 +0,0 @@
SUBSYSTEM=="virtio-ports", ATTR{name}=="org.jdrupes.vmop_agent.0", \
TAG+="systemd" ENV{SYSTEMD_WANTS}="vmop-agent.service"

View file

@ -1,3 +0,0 @@
#!/bin/sh
sed -i '/AutomaticLogin/d' /etc/gdm/custom.conf

View file

@ -1,146 +0,0 @@
#!/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

View file

@ -1,15 +0,0 @@
[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

View file

@ -1,20 +0,0 @@
# Example setup
The CRD must be deployed independently.
```sh
kubectl apply -f https://github.com/mnlipp/VM-Operator/raw/main/deploy/crds/vms-crd.yaml
```
Apart from that, the `kustomize.yaml` defines a namespace for the manager
(and the VMs managed by it) and patches the repository PVC to create
a small volume using local-path.
A second patch provides a new configuration file for the manager
that makes it use the local-path storage class when creating the
small volume for a runner's data.
The `kustomize.yaml` does not include the test VM. Before creating
the test VM, you will again most likely want to change the
disk definition. The sample file uses a reference to some
manually allocated PV.

View file

@ -1,38 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- https://github.com/mnlipp/VM-Operator/deploy
namespace: vmop-demo
patches:
- patch: |-
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: vmop-image-repository
spec:
# Default is ReadOnlyMany
accessModes:
- ReadWriteOnce
resources:
requests:
# Default is 100Gi
storage: 10Gi
# Default is to use the default storage class
storageClassName: local-path
- patch: |-
kind: ConfigMap
apiVersion: v1
metadata:
name: vm-operator
data:
config.yaml: |
"/Manager":
"/Controller":
"/Reconciler"
runnerDataPvc:
# Default is to use the default storage class
storageClassName: local-path

View file

@ -1,45 +0,0 @@
apiVersion: "vmoperator.jdrupes.org/v1"
kind: VirtualMachine
metadata:
namespace: vmop-demo
name: test-vm
spec:
# image:
# Defaults:
# repository: ghcr.io
# path: mnlipp/org.jdrupes.vmoperator.runner.qemu-arch
# version: latest
# pullPolicy: Always
vm:
maximumCpus: 4
currentCpus: 2
maximumRam: 8Gi
currentRam: 4Gi
networks:
- user: {}
disks:
- volumeClaimTemplate:
metadata:
name: system
spec:
storageClassName: ""
selector:
matchLabels:
app.kubernetes.io/name: vmrunner
app.kubernetes.io/instance: test-vm
vmrunner.jdrupes.org/disk: system
resources:
requests:
storage: 40Gi
- cdrom:
image: ""
# image: https://download.fedoraproject.org/pub/fedora/linux/releases/38/Workstation/x86_64/iso/Fedora-Workstation-Live-x86_64-38-1.6.iso
# image: "Fedora-Workstation-Live-x86_64-38-1.6.iso"
display:
spice:
port: 5910

View file

@ -1,15 +0,0 @@
# Example setup
The CRD must be deployed independently.
```sh
kubectl apply -f https://github.com/mnlipp/VM-Operator/raw/main/deploy/crds/vms-crd.yaml
```
Apart from that, the `kustomize.yaml` defines a namespace for the manager
(and the VMs managed by it) and applies patches to use `rook-cephfs` as
storage class (instead of the default storage class).
The `kustomize.yaml` does not include the test VM. Before creating
the test VM, you will again most likely want to change the
disk definition. The sample file claims a ceph block device.

View file

@ -1,31 +0,0 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- https://github.com/mnlipp/VM-Operator/deploy
namespace: vmop-demo
patches:
# Use storage class rook-cephfs for the shared image repository
- patch: |-
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: vmop-image-repository
spec:
storageClassName: rook-cephfs
# Use storage class rook-cepfs for the runner's data (e.g. EFI vars)
- patch: |-
kind: ConfigMap
apiVersion: v1
metadata:
name: vm-operator
data:
config.yaml: |
"/Manager":
"/Controller":
"/Reconciler":
runnerData:
storageClassName: rook-cephfs

View file

@ -1,40 +0,0 @@
apiVersion: "vmoperator.jdrupes.org/v1"
kind: VirtualMachine
metadata:
namespace: vmop-demo
name: test-vm
spec:
# image:
# Defaults:
# repository: ghcr.io
# path: mnlipp/org.jdrupes.vmoperator.runner.qemu-arch
# version: latest
# pullPolicy: Always
vm:
maximumCpus: 4
currentCpus: 2
maximumRam: 8Gi
currentRam: 4Gi
networks:
- user: {}
disks:
- volumeClaimTemplate:
metadata:
name: system
spec:
storageClassName: rook-ceph-block
resources:
requests:
storage: 40Gi
- cdrom:
# image: ""
image: https://download.fedoraproject.org/pub/fedora/linux/releases/38/Workstation/x86_64/iso/Fedora-Workstation-Live-x86_64-38-1.6.iso
# image: "Fedora-Workstation-Live-x86_64-38-1.6.iso"
display:
spice:
port: 5910

View file

@ -1 +0,0 @@
org.gradle.parallel=true

View file

@ -1,305 +0,0 @@
org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=20
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=20
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=20
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=20
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=20
org.eclipse.jdt.core.formatter.alignment_for_assignment=20
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=20
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=84
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=16
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=16
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_module_statements=16
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=16
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=20
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_type_arguments=16
org.eclipse.jdt.core.formatter.alignment_for_type_parameters=16
org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false
org.eclipse.jdt.core.formatter.comment.format_block_comments=true
org.eclipse.jdt.core.formatter.comment.format_header=false
org.eclipse.jdt.core.formatter.comment.format_html=false
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=false
org.eclipse.jdt.core.formatter.comment.format_line_comments=true
org.eclipse.jdt.core.formatter.comment.format_source_code=false
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
org.eclipse.jdt.core.formatter.comment.indent_root_tags=false
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert
org.eclipse.jdt.core.formatter.comment.line_length=80
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=true
org.eclipse.jdt.core.formatter.compact_else_if=true
org.eclipse.jdt.core.formatter.continuation_indentation=2
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=1
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=false
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_empty_lines=false
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
org.eclipse.jdt.core.formatter.indentation.size=4
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
org.eclipse.jdt.core.formatter.join_lines_in_comments=false
org.eclipse.jdt.core.formatter.join_wrapped_lines=false
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
org.eclipse.jdt.core.formatter.lineSplit=80
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=true
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=true
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines
org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
org.eclipse.jdt.core.formatter.tabulation.char=space
org.eclipse.jdt.core.formatter.tabulation.size=4
org.eclipse.jdt.core.formatter.use_on_off_tags=false
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true
org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=true
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter

Binary file not shown.

View file

@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

249
gradlew vendored
View file

@ -1,249 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

92
gradlew.bat vendored
View file

@ -1,92 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View file

@ -1,2 +0,0 @@
:root { --body-font-size: 16px;}
:root { --code-font-size: 16px;}

View file

@ -1,10 +0,0 @@
<html lang="de">
<head>
<title>JGrapes documentation</title>
<meta http-equiv="refresh" content="0; url=http://mnlipp.github.io/jgrapes/" />
</head>
<body>
If you are not redirected automatically,
follow this <a href='http://mnlipp.github.io/jgrapes/'>link to the documentation.</a>.
</body>
</html>

View file

@ -1,36 +0,0 @@
<div>
<!-- Optional footer data -->
<p id=githubfooter style="padding: 5px 6px; visibility: hidden">Hosted on GitHub Pages &mdash;
<a href="https://github.com/site/terms" target="_top">Terms</a>
&mdash; <a href="https://github.com/site/privacy" target="_top">Privacy</a></p>
<script type="text/javascript">
if (location.hostname.indexOf("github") !== -1 || location.hostname.indexOf("jdrupes.org") !== -1) {
document.getElementById("githubfooter").style.visibility="visible";
}
</script>
<noscript>
<div>JavaScript is disabled on your browser, terms and privacy links may not be shown correctly.</div>
</noscript>
<!-- Matomo anonymous, no cookies (https://matomo.org/blog/2018/04/how-to-not-process-any-personal-data-with-matomo-and-what-it-means-for-you/) -->
<script>
var _paq = _paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(["setDocumentTitle", document.domain + "/" + document.title]);
_paq.push(["setCookieDomain", "*.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&amp;rec=1&amp;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>

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<fileset-config file-format-version="1.2.0" simple-config="false" sync-formatter="false">
<local-check-config name="Project Checks" location="/VM-Operator/checkstyle.xml" type="project" description="">
<additional-data name="protect-config-file" value="false"/>
</local-check-config>
<fileset name="all" enabled="true" check-config-name="Project Checks" local="true">
<file-match-pattern match-pattern="." include-pattern="true"/>
</fileset>
</fileset-config>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<eclipse-pmd xmlns="http://acanda.ch/eclipse-pmd/0.8" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://acanda.ch/eclipse-pmd/0.8 http://acanda.ch/eclipse-pmd/eclipse-pmd-0.8.xsd">
<analysis enabled="true" />
<rulesets>
<ruleset name="Custom Rules" ref="VM-Operator/ruleset.xml" refcontext="workspace" />
</rulesets>
</eclipse-pmd>

View file

@ -1,7 +0,0 @@
add_header=true
eclipse.preferences.version=1
header_text=/*\n * VM-Operator\n * Copyright (C) 2024 Michael N. Lipp\n * \n * This program is free software\: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program. If not, see <https\://www.gnu.org/licenses/>.\n */
project_specific_settings=true
visibility_package=false
visibility_private=false
visibility_protected=false

View file

@ -1,13 +0,0 @@
arguments=
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=..
eclipse.preferences.version=1
gradle.user.home=
java.home=
jvm.arguments=
offline.mode=false
override.workspace.settings=false
show.console.view=false
show.executions.view=false

View file

@ -1,2 +0,0 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8

View file

@ -1,2 +0,0 @@
eclipse.preferences.version=1
line.separator=\n

View file

@ -1,17 +0,0 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This project uses @Incubating APIs which are subject to change.
*/
plugins {
id 'org.jdrupes.vmoperator.java-library-conventions'
}
dependencies {
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 'org.yaml:snakeyaml'
api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:[2.16.1,3]'
}

View file

@ -1,127 +0,0 @@
/*
* VM-Operator
* Copyright (C) 2023 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.common;
/**
* Some constants.
*/
@SuppressWarnings("PMD.DataClass")
public class Constants {
/** The Constant APP_NAME. */
public static final String APP_NAME = "vm-runner";
/** The Constant VM_OP_NAME. */
public static final String VM_OP_NAME = "vm-operator";
/**
* 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 KIND_VM. */
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";
}
}

View file

@ -1,113 +0,0 @@
/*
* VM-Operator
* Copyright (C) 2023 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.common;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Provides methods for parsing "official" memory sizes..
*/
@SuppressWarnings("PMD.UseUtilityClass")
public class Convertions {
@SuppressWarnings({ "PMD.UseConcurrentHashMap",
"PMD.FieldNamingConventions" })
private static final Map<String, BigInteger> unitMap = new HashMap<>();
@SuppressWarnings({ "PMD.FieldNamingConventions" })
private static final List<Map.Entry<String, BigInteger>> unitMappings;
@SuppressWarnings({ "PMD.FieldNamingConventions" })
private static final Pattern memorySize
= Pattern.compile("^\\s*(\\d+(\\.\\d+)?)\\s*([A-Za-z]*)\\s*");
static {
// SI units and common abbreviations
BigInteger factor = BigInteger.ONE;
unitMap.put("", factor);
BigInteger scale = BigInteger.valueOf(1000);
for (var unit : List.of("B", "kB", "MB", "GB", "TB", "PB", "EB")) {
unitMap.put(unit, factor);
factor = factor.multiply(scale);
}
// Binary units
factor = BigInteger.valueOf(1024);
scale = BigInteger.valueOf(1024);
for (var unit : List.of("KiB", "MiB", "GiB", "TiB", "PiB", "EiB")) {
unitMap.put(unit, factor);
factor = factor.multiply(scale);
}
unitMappings = unitMap.entrySet().stream()
.sorted((a, b) -> -1 * a.getValue().compareTo(b.getValue()))
.toList();
}
/**
* Parses a memory size specification.
*
* @param amount the amount
* @return the big integer
*/
public static BigInteger parseMemory(Object amount) {
if (amount == null) {
return (BigInteger) amount;
}
if (amount instanceof BigInteger number) {
return number;
}
if (amount instanceof Number number) {
return BigInteger.valueOf(number.longValue());
}
var matcher = memorySize.matcher(amount.toString());
if (!matcher.matches()) {
throw new NumberFormatException(amount.toString());
}
var unit = BigInteger.ONE;
if (matcher.group(3) != null) {
unit = unitMap.get(matcher.group(3));
if (unit == null) {
throw new NumberFormatException("Illegal unit \""
+ matcher.group(3) + "\" in \"" + amount.toString() + "\"");
}
}
var number = matcher.group(1);
return new BigDecimal(number).multiply(new BigDecimal(unit))
.toBigInteger();
}
/**
* Format memory size for humans.
*
* @param size the size
* @return the string
*/
public static String formatMemory(BigInteger size) {
for (var mapping : unitMappings) {
if (size.compareTo(mapping.getValue()) >= 0
&& size.mod(mapping.getValue()).equals(BigInteger.ZERO)) {
return (size.divide(mapping.getValue()).toString()
+ " " + mapping.getKey()).trim();
}
}
return size.toString();
}
}

View file

@ -1,197 +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.InstanceCreator;
import com.google.gson.JsonObject;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import io.kubernetes.client.openapi.ApiClient;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
/**
* A factory for creating objects.
*
* @param <O> the generic type
* @param <L> the generic type
*/
public class DynamicTypeAdapterFactory<O extends K8sDynamicModel,
L extends K8sDynamicModelsBase<O>> implements TypeAdapterFactory {
private final Class<O> objectClass;
private final Class<L> objectListClass;
/**
* Make sure that this adapter is registered.
*
* @param client the client
*/
public void register(ApiClient client) {
if (!ModelCreator.class
.equals(client.getJSON().getGson().getAdapter(objectClass)
.getClass())
|| !ModelsCreator.class.equals(client.getJSON().getGson()
.getAdapter(objectListClass).getClass())) {
Gson gson = client.getJSON().getGson();
client.getJSON().setGson(gson.newBuilder()
.registerTypeAdapterFactory(this).create());
}
}
/**
* Instantiates a new generic type adapter factory.
*
* @param objectClass the object class
* @param objectListClass the object list class
*/
public DynamicTypeAdapterFactory(Class<O> objectClass,
Class<L> objectListClass) {
this.objectClass = objectClass;
this.objectListClass = objectListClass;
}
/**
* Creates a type adapter for the given type.
*
* @param <T> the generic type
* @param gson the gson
* @param typeToken the type token
* @return the type adapter or null if the type is not handles by
* this factory
*/
@SuppressWarnings("unchecked")
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
if (TypeToken.get(objectClass).equals(typeToken)) {
return (TypeAdapter<T>) new ModelCreator(gson);
}
if (TypeToken.get(objectListClass).equals(typeToken)) {
return (TypeAdapter<T>) new ModelsCreator(gson);
}
return null;
}
/**
* The Class ModelCreator.
*/
private class ModelCreator extends TypeAdapter<O>
implements InstanceCreator<O> {
private final Gson delegate;
/**
* Instantiates a new object state creator.
*
* @param delegate the delegate
*/
public ModelCreator(Gson delegate) {
this.delegate = delegate;
}
@Override
public O createInstance(Type type) {
try {
return objectClass.getConstructor(Gson.class, JsonObject.class)
.newInstance(delegate, null);
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
return null;
}
}
@Override
public void write(JsonWriter jsonWriter, O state)
throws IOException {
jsonWriter.jsonValue(delegate.toJson(state.data()));
}
@Override
public O read(JsonReader jsonReader)
throws IOException {
try {
return objectClass.getConstructor(Gson.class, JsonObject.class)
.newInstance(delegate,
delegate.fromJson(jsonReader, JsonObject.class));
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
return null;
}
}
}
/**
* The Class ModelsCreator.
*/
private class ModelsCreator extends TypeAdapter<L>
implements InstanceCreator<L> {
private final Gson delegate;
/**
* Instantiates a new object states creator.
*
* @param delegate the delegate
*/
public ModelsCreator(Gson delegate) {
this.delegate = delegate;
}
@Override
public L createInstance(Type type) {
try {
return objectListClass
.getConstructor(Gson.class, JsonObject.class)
.newInstance(delegate, null);
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
return null;
}
}
@Override
public void write(JsonWriter jsonWriter, L states)
throws IOException {
jsonWriter.jsonValue(delegate.toJson(states.data()));
}
@Override
public L read(JsonReader jsonReader)
throws IOException {
try {
return objectListClass
.getConstructor(Gson.class, JsonObject.class)
.newInstance(delegate,
delegate.fromJson(jsonReader, JsonObject.class));
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
return null;
}
}
}
}

View file

@ -1,249 +0,0 @@
/*
* VM-Operator
* Copyright (C) 2023,2024 Michael N. Lipp
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jdrupes.vmoperator.common;
import com.google.gson.JsonObject;
import io.kubernetes.client.Discovery;
import io.kubernetes.client.Discovery.APIResource;
import io.kubernetes.client.common.KubernetesListObject;
import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.common.KubernetesType;
import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.EventsV1Api;
import io.kubernetes.client.openapi.models.EventsV1Event;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1ObjectReference;
import io.kubernetes.client.util.Strings;
import io.kubernetes.client.util.generic.GenericKubernetesApi;
import io.kubernetes.client.util.generic.KubernetesApiResponse;
import io.kubernetes.client.util.generic.options.PatchOptions;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.time.OffsetDateTime;
import java.util.Map;
import java.util.Optional;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
/**
* Helpers for K8s API.
*/
@SuppressWarnings({ "PMD.ShortClassName", "PMD.UseUtilityClass" })
public class K8s {
/**
* Returns the result from an API call as {@link Optional} if the
* call was successful. Returns an empty `Optional` if the status
* code is 404 (not found). Else throws an exception.
*
* @param <T> the generic type
* @param response the response
* @return the optional
* @throws ApiException the API exception
*/
public static <T extends KubernetesType> Optional<T>
optional(KubernetesApiResponse<T> response) throws ApiException {
if (response.isSuccess()) {
return Optional.of(response.getObject());
}
if (response.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return Optional.empty();
}
response.throwsApiException();
// Never reached
return Optional.empty();
}
/**
* Returns a new context with the given version as preferred version.
*
* @param context the context
* @param version the version
* @return the API resource
*/
public static APIResource preferred(APIResource context, String version) {
assert context.getVersions().contains(version);
return new APIResource(context.getGroup(),
context.getVersions(), version, context.getKind(),
context.getNamespaced(), context.getResourcePlural(),
context.getResourceSingular());
}
/**
* Return a string representation of the context (API resource).
*
* @param context the context
* @return the string
*/
@SuppressWarnings("PMD.UseLocaleWithCaseConversions")
public static String toString(APIResource context) {
return (Strings.isNullOrEmpty(context.getGroup()) ? ""
: context.getGroup() + "/")
+ context.getPreferredVersion().toUpperCase()
+ context.getKind();
}
/**
* Convert Yaml to Json.
*
* @param client the client
* @param yaml the yaml
* @return the json element
*/
public static JsonObject yamlToJson(ApiClient client, Reader yaml) {
// Avoid Yaml.load due to
// https://github.com/kubernetes-client/java/issues/2741
Map<String, Object> yamlData
= new Yaml(new SafeConstructor(new LoaderOptions())).load(yaml);
// There's no short-cut from Java (collections) to Gson
var gson = client.getJSON().getGson();
var jsonText = gson.toJson(yamlData);
return gson.fromJson(jsonText, JsonObject.class);
}
/**
* Lookup the specified API resource. If the version is `null` or
* empty, the preferred version in the result is the default
* returned from the server.
*
* @param client the client
* @param group the group
* @param version the version
* @param kind the kind
* @return the optional
* @throws ApiException the api exception
*/
public static Optional<APIResource> context(ApiClient client,
String group, String version, String kind) throws ApiException {
var apiMatch = new Discovery(client).findAll().stream()
.filter(r -> r.getGroup().equals(group) && r.getKind().equals(kind)
&& (Strings.isNullOrEmpty(version)
|| r.getVersions().contains(version)))
.findFirst();
if (apiMatch.isEmpty()) {
return Optional.empty();
}
var apiRes = apiMatch.get();
if (!Strings.isNullOrEmpty(version)) {
if (!apiRes.getVersions().contains(version)) {
return Optional.empty();
}
apiRes = new APIResource(apiRes.getGroup(), apiRes.getVersions(),
version, apiRes.getKind(), apiRes.getNamespaced(),
apiRes.getResourcePlural(), apiRes.getResourceSingular());
}
return Optional.of(apiRes);
}
/**
* Apply the given patch data.
*
* @param <T> the generic type
* @param <LT> the generic type
* @param api the api
* @param existing the existing
* @param update the update
* @return the t
* @throws ApiException the api exception
*/
@SuppressWarnings("PMD.GenericsNaming")
public static <T extends KubernetesObject, LT extends KubernetesListObject>
T apply(GenericKubernetesApi<T, LT> api, T existing, String update)
throws ApiException {
PatchOptions opts = new PatchOptions();
opts.setForce(true);
opts.setFieldManager("kubernetes-java-kubectl-apply");
var response = api.patch(existing.getMetadata().getNamespace(),
existing.getMetadata().getName(), V1Patch.PATCH_FORMAT_APPLY_YAML,
new V1Patch(update), opts).throwsApiException();
return response.getObject();
}
/**
* Create an object reference.
*
* @param object the object
* @return the v 1 object reference
*/
public static V1ObjectReference
objectReference(KubernetesObject object) {
return new V1ObjectReference().apiVersion(object.getApiVersion())
.kind(object.getKind())
.namespace(object.getMetadata().getNamespace())
.name(object.getMetadata().getName())
.resourceVersion(object.getMetadata().getResourceVersion())
.uid(object.getMetadata().getUid());
}
/**
* Creates an event related to the object, adding reasonable defaults.
*
* * If `kind` is not set, it is set to "Event".
* * If `metadata.namespace` is not set, it is set
* to the object's namespace.
* * If neither `metadata.name` nor `matadata.generateName` are set,
* set `generateName` to the object's name with a dash appended.
* * If `reportingInstance` is not set, set it to the object's name.
* * If `eventTime` is not set, set it to now.
* * If `type` is not set, set it to "Normal"
* * If `regarding` is not set, set it to the given object.
*
* @param client the client
* @param object the object
* @param event the event
* @throws ApiException the api exception
*/
@SuppressWarnings("PMD.NPathComplexity")
public static void createEvent(ApiClient client,
KubernetesObject object, EventsV1Event event)
throws ApiException {
if (Strings.isNullOrEmpty(event.getKind())) {
event.kind("Event");
}
if (event.getMetadata() == null) {
event.metadata(new V1ObjectMeta());
}
if (Strings.isNullOrEmpty(event.getMetadata().getNamespace())) {
event.getMetadata().namespace(object.getMetadata().getNamespace());
}
if (Strings.isNullOrEmpty(event.getMetadata().getName())
&& Strings.isNullOrEmpty(event.getMetadata().getGenerateName())) {
event.getMetadata()
.generateName(object.getMetadata().getName() + "-");
}
if (Strings.isNullOrEmpty(event.getReportingInstance())) {
event.reportingInstance(object.getMetadata().getName());
}
if (event.getEventTime() == null) {
event.eventTime(OffsetDateTime.now());
}
if (Strings.isNullOrEmpty(event.getType())) {
event.type("Normal");
}
if (event.getRegarding() == null) {
event.regarding(objectReference(object));
}
new EventsV1Api(client).createNamespacedEvent(
object.getMetadata().getNamespace(), event, null, null, null, null);
}
}

View file

@ -1,954 +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 io.kubernetes.client.openapi.ApiCallback;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.ApiResponse;
import io.kubernetes.client.openapi.JSON;
import io.kubernetes.client.openapi.Pair;
import io.kubernetes.client.openapi.auth.Authentication;
import io.kubernetes.client.util.ClientBuilder;
import io.kubernetes.client.util.generic.options.PatchOptions;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.net.ssl.KeyManager;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Request.Builder;
import okhttp3.RequestBody;
import okhttp3.Response;
/**
* A client with some additional properties.
*/
@SuppressWarnings({ "PMD.ExcessivePublicCount", "PMD.TooManyMethods",
"checkstyle:LineLength", "PMD.CouplingBetweenObjects", "PMD.GodClass" })
public class K8sClient extends ApiClient {
private ApiClient apiClient;
private PatchOptions defaultPatchOptions;
/**
* Instantiates a new client.
*
* @throws IOException Signals that an I/O exception has occurred.
*/
public K8sClient() throws IOException {
defaultPatchOptions = new PatchOptions();
defaultPatchOptions.setFieldManager("kubernetes-java-kubectl-apply");
}
private ApiClient apiClient() {
if (apiClient == null) {
try {
apiClient = ClientBuilder.standard().build();
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
return apiClient;
}
/**
* Gets the default patch options.
*
* @return the defaultPatchOptions
*/
public PatchOptions defaultPatchOptions() {
return defaultPatchOptions;
}
/**
* Changes the default patch options.
*
* @param patchOptions the patch options
* @return the client
*/
public K8sClient with(PatchOptions patchOptions) {
defaultPatchOptions = patchOptions;
return this;
}
/**
* Gets the base path.
*
* @return the base path
* @see ApiClient#getBasePath()
*/
@Override
public String getBasePath() {
return apiClient().getBasePath();
}
/**
* Sets the base path.
*
* @param basePath the base path
* @return the api client
* @see ApiClient#setBasePath(java.lang.String)
*/
@Override
public ApiClient setBasePath(String basePath) {
return apiClient().setBasePath(basePath);
}
/**
* Gets the http client.
*
* @return the http client
* @see ApiClient#getHttpClient()
*/
@Override
public OkHttpClient getHttpClient() {
return apiClient().getHttpClient();
}
/**
* Sets the http client.
*
* @param newHttpClient the new http client
* @return the api client
* @see ApiClient#setHttpClient(okhttp3.OkHttpClient)
*/
@Override
public ApiClient setHttpClient(OkHttpClient newHttpClient) {
return apiClient().setHttpClient(newHttpClient);
}
/**
* Gets the json.
*
* @return the json
* @see ApiClient#getJSON()
*/
@SuppressWarnings("abbreviationAsWordInName")
@Override
public JSON getJSON() {
return apiClient().getJSON();
}
/**
* Sets the JSON.
*
* @param json the json
* @return the api client
* @see ApiClient#setJSON(io.kubernetes.client.openapi.JSON)
*/
@SuppressWarnings("abbreviationAsWordInName")
@Override
public ApiClient setJSON(JSON json) {
return apiClient().setJSON(json);
}
/**
* Checks if is verifying ssl.
*
* @return true, if is verifying ssl
* @see ApiClient#isVerifyingSsl()
*/
@Override
public boolean isVerifyingSsl() {
return apiClient().isVerifyingSsl();
}
/**
* Sets the verifying ssl.
*
* @param verifyingSsl the verifying ssl
* @return the api client
* @see ApiClient#setVerifyingSsl(boolean)
*/
@Override
public ApiClient setVerifyingSsl(boolean verifyingSsl) {
return apiClient().setVerifyingSsl(verifyingSsl);
}
/**
* Gets the ssl ca cert.
*
* @return the ssl ca cert
* @see ApiClient#getSslCaCert()
*/
@Override
public InputStream getSslCaCert() {
return apiClient().getSslCaCert();
}
/**
* Sets the ssl ca cert.
*
* @param sslCaCert the ssl ca cert
* @return the api client
* @see ApiClient#setSslCaCert(java.io.InputStream)
*/
@Override
public ApiClient setSslCaCert(InputStream sslCaCert) {
return apiClient().setSslCaCert(sslCaCert);
}
/**
* Gets the key managers.
*
* @return the key managers
* @see ApiClient#getKeyManagers()
*/
@Override
public KeyManager[] getKeyManagers() {
return apiClient().getKeyManagers();
}
/**
* Sets the key managers.
*
* @param managers the managers
* @return the api client
* @see ApiClient#setKeyManagers(javax.net.ssl.KeyManager[])
*/
@Override
public ApiClient setKeyManagers(KeyManager[] managers) {
return apiClient().setKeyManagers(managers);
}
/**
* Gets the date format.
*
* @return the date format
* @see ApiClient#getDateFormat()
*/
@Override
public DateFormat getDateFormat() {
return apiClient().getDateFormat();
}
/**
* Sets the date format.
*
* @param dateFormat the date format
* @return the api client
* @see ApiClient#setDateFormat(java.text.DateFormat)
*/
@Override
public ApiClient setDateFormat(DateFormat dateFormat) {
return apiClient().setDateFormat(dateFormat);
}
/**
* Sets the sql date format.
*
* @param dateFormat the date format
* @return the api client
* @see ApiClient#setSqlDateFormat(java.text.DateFormat)
*/
@Override
public ApiClient setSqlDateFormat(DateFormat dateFormat) {
return apiClient().setSqlDateFormat(dateFormat);
}
/**
* Sets the offset date time format.
*
* @param dateFormat the date format
* @return the api client
* @see ApiClient#setOffsetDateTimeFormat(java.time.format.DateTimeFormatter)
*/
@Override
public ApiClient setOffsetDateTimeFormat(DateTimeFormatter dateFormat) {
return apiClient().setOffsetDateTimeFormat(dateFormat);
}
/**
* Sets the local date format.
*
* @param dateFormat the date format
* @return the api client
* @see ApiClient#setLocalDateFormat(java.time.format.DateTimeFormatter)
*/
@Override
public ApiClient setLocalDateFormat(DateTimeFormatter dateFormat) {
return apiClient().setLocalDateFormat(dateFormat);
}
/**
* Sets the lenient on json.
*
* @param lenientOnJson the lenient on json
* @return the api client
* @see ApiClient#setLenientOnJson(boolean)
*/
@Override
public ApiClient setLenientOnJson(boolean lenientOnJson) {
return apiClient().setLenientOnJson(lenientOnJson);
}
/**
* Gets the authentications.
*
* @return the authentications
* @see ApiClient#getAuthentications()
*/
@Override
public Map<String, Authentication> getAuthentications() {
return apiClient().getAuthentications();
}
/**
* Gets the authentication.
*
* @param authName the auth name
* @return the authentication
* @see ApiClient#getAuthentication(java.lang.String)
*/
@Override
public Authentication getAuthentication(String authName) {
return apiClient().getAuthentication(authName);
}
/**
* Sets the username.
*
* @param username the new username
* @see ApiClient#setUsername(java.lang.String)
*/
@Override
public void setUsername(String username) {
apiClient().setUsername(username);
}
/**
* Sets the password.
*
* @param password the new password
* @see ApiClient#setPassword(java.lang.String)
*/
@Override
public void setPassword(String password) {
apiClient().setPassword(password);
}
/**
* Sets the api key.
*
* @param apiKey the new api key
* @see ApiClient#setApiKey(java.lang.String)
*/
@Override
public void setApiKey(String apiKey) {
apiClient().setApiKey(apiKey);
}
/**
* Sets the api key prefix.
*
* @param apiKeyPrefix the new api key prefix
* @see ApiClient#setApiKeyPrefix(java.lang.String)
*/
@Override
public void setApiKeyPrefix(String apiKeyPrefix) {
apiClient().setApiKeyPrefix(apiKeyPrefix);
}
/**
* Sets the access token.
*
* @param accessToken the new access token
* @see ApiClient#setAccessToken(java.lang.String)
*/
@Override
public void setAccessToken(String accessToken) {
apiClient().setAccessToken(accessToken);
}
/**
* Sets the user agent.
*
* @param userAgent the user agent
* @return the api client
* @see ApiClient#setUserAgent(java.lang.String)
*/
@Override
public ApiClient setUserAgent(String userAgent) {
return apiClient().setUserAgent(userAgent);
}
/**
* To string.
*
* @return the string
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return apiClient().toString();
}
/**
* Adds the default header.
*
* @param key the key
* @param value the value
* @return the api client
* @see ApiClient#addDefaultHeader(java.lang.String, java.lang.String)
*/
@Override
public ApiClient addDefaultHeader(String key, String value) {
return apiClient().addDefaultHeader(key, value);
}
/**
* Adds the default cookie.
*
* @param key the key
* @param value the value
* @return the api client
* @see ApiClient#addDefaultCookie(java.lang.String, java.lang.String)
*/
@Override
public ApiClient addDefaultCookie(String key, String value) {
return apiClient().addDefaultCookie(key, value);
}
/**
* Checks if is debugging.
*
* @return true, if is debugging
* @see ApiClient#isDebugging()
*/
@Override
public boolean isDebugging() {
return apiClient().isDebugging();
}
/**
* Sets the debugging.
*
* @param debugging the debugging
* @return the api client
* @see ApiClient#setDebugging(boolean)
*/
@Override
public ApiClient setDebugging(boolean debugging) {
return apiClient().setDebugging(debugging);
}
/**
* Gets the temp folder path.
*
* @return the temp folder path
* @see ApiClient#getTempFolderPath()
*/
@Override
public String getTempFolderPath() {
return apiClient().getTempFolderPath();
}
/**
* Sets the temp folder path.
*
* @param tempFolderPath the temp folder path
* @return the api client
* @see ApiClient#setTempFolderPath(java.lang.String)
*/
@Override
public ApiClient setTempFolderPath(String tempFolderPath) {
return apiClient().setTempFolderPath(tempFolderPath);
}
/**
* Gets the connect timeout.
*
* @return the connect timeout
* @see ApiClient#getConnectTimeout()
*/
@Override
public int getConnectTimeout() {
return apiClient().getConnectTimeout();
}
/**
* Sets the connect timeout.
*
* @param connectionTimeout the connection timeout
* @return the api client
* @see ApiClient#setConnectTimeout(int)
*/
@Override
public ApiClient setConnectTimeout(int connectionTimeout) {
return apiClient().setConnectTimeout(connectionTimeout);
}
/**
* Gets the read timeout.
*
* @return the read timeout
* @see ApiClient#getReadTimeout()
*/
@Override
public int getReadTimeout() {
return apiClient().getReadTimeout();
}
/**
* Sets the read timeout.
*
* @param readTimeout the read timeout
* @return the api client
* @see ApiClient#setReadTimeout(int)
*/
@Override
public ApiClient setReadTimeout(int readTimeout) {
return apiClient().setReadTimeout(readTimeout);
}
/**
* Gets the write timeout.
*
* @return the write timeout
* @see ApiClient#getWriteTimeout()
*/
@Override
public int getWriteTimeout() {
return apiClient().getWriteTimeout();
}
/**
* Sets the write timeout.
*
* @param writeTimeout the write timeout
* @return the api client
* @see ApiClient#setWriteTimeout(int)
*/
@Override
public ApiClient setWriteTimeout(int writeTimeout) {
return apiClient().setWriteTimeout(writeTimeout);
}
/**
* Parameter to string.
*
* @param param the param
* @return the string
* @see ApiClient#parameterToString(java.lang.Object)
*/
@Override
public String parameterToString(Object param) {
return apiClient().parameterToString(param);
}
/**
* Parameter to pair.
*
* @param name the name
* @param value the value
* @return the list
* @see ApiClient#parameterToPair(java.lang.String, java.lang.Object)
*/
@Override
public List<Pair> parameterToPair(String name, Object value) {
return apiClient().parameterToPair(name, value);
}
/**
* Parameter to pairs.
*
* @param collectionFormat the collection format
* @param name the name
* @param value the value
* @return the list
* @see ApiClient#parameterToPairs(java.lang.String, java.lang.String, java.util.Collection)
*/
@SuppressWarnings({ "rawtypes", "PMD.AvoidDuplicateLiterals" })
@Override
public List<Pair> parameterToPairs(String collectionFormat, String name,
Collection value) {
return apiClient().parameterToPairs(collectionFormat, name, value);
}
/**
* Collection path parameter to string.
*
* @param collectionFormat the collection format
* @param value the value
* @return the string
* @see ApiClient#collectionPathParameterToString(java.lang.String, java.util.Collection)
*/
@SuppressWarnings("rawtypes")
@Override
public String collectionPathParameterToString(String collectionFormat,
Collection value) {
return apiClient().collectionPathParameterToString(collectionFormat,
value);
}
/**
* Sanitize filename.
*
* @param filename the filename
* @return the string
* @see ApiClient#sanitizeFilename(java.lang.String)
*/
@Override
public String sanitizeFilename(String filename) {
return apiClient().sanitizeFilename(filename);
}
/**
* Checks if is json mime.
*
* @param mime the mime
* @return true, if is json mime
* @see ApiClient#isJsonMime(java.lang.String)
*/
@Override
public boolean isJsonMime(String mime) {
return apiClient().isJsonMime(mime);
}
/**
* Select header accept.
*
* @param accepts the accepts
* @return the string
* @see ApiClient#selectHeaderAccept(java.lang.String[])
*/
@Override
public String selectHeaderAccept(String[] accepts) {
return apiClient().selectHeaderAccept(accepts);
}
/**
* Select header content type.
*
* @param contentTypes the content types
* @return the string
* @see ApiClient#selectHeaderContentType(java.lang.String[])
*/
@Override
public String selectHeaderContentType(String[] contentTypes) {
return apiClient().selectHeaderContentType(contentTypes);
}
/**
* Escape string.
*
* @param str the str
* @return the string
* @see ApiClient#escapeString(java.lang.String)
*/
@Override
public String escapeString(String str) {
return apiClient().escapeString(str);
}
/**
* Deserialize.
*
* @param <T> the generic type
* @param response the response
* @param returnType the return type
* @return the t
* @throws ApiException the api exception
* @see ApiClient#deserialize(okhttp3.Response, java.lang.reflect.Type)
*/
@Override
public <T> T deserialize(Response response, Type returnType)
throws ApiException {
return apiClient().deserialize(response, returnType);
}
/**
* Serialize.
*
* @param obj the obj
* @param contentType the content type
* @return the request body
* @throws ApiException the api exception
* @see ApiClient#serialize(java.lang.Object, java.lang.String)
*/
@Override
public RequestBody serialize(Object obj, String contentType)
throws ApiException {
return apiClient().serialize(obj, contentType);
}
/**
* Download file from response.
*
* @param response the response
* @return the file
* @throws ApiException the api exception
* @see ApiClient#downloadFileFromResponse(okhttp3.Response)
*/
@Override
public File downloadFileFromResponse(Response response)
throws ApiException {
return apiClient().downloadFileFromResponse(response);
}
/**
* Prepare download file.
*
* @param response the response
* @return the file
* @throws IOException Signals that an I/O exception has occurred.
* @see ApiClient#prepareDownloadFile(okhttp3.Response)
*/
@Override
public File prepareDownloadFile(Response response) throws IOException {
return apiClient().prepareDownloadFile(response);
}
/**
* Execute.
*
* @param <T> the generic type
* @param call the call
* @return the api response
* @throws ApiException the api exception
* @see ApiClient#execute(okhttp3.Call)
*/
@Override
public <T> ApiResponse<T> execute(Call call) throws ApiException {
return apiClient().execute(call);
}
/**
* Execute.
*
* @param <T> the generic type
* @param call the call
* @param returnType the return type
* @return the api response
* @throws ApiException the api exception
* @see ApiClient#execute(okhttp3.Call, java.lang.reflect.Type)
*/
@Override
public <T> ApiResponse<T> execute(Call call, Type returnType)
throws ApiException {
return apiClient().execute(call, returnType);
}
/**
* Execute async.
*
* @param <T> the generic type
* @param call the call
* @param callback the callback
* @see ApiClient#executeAsync(okhttp3.Call, io.kubernetes.client.openapi.ApiCallback)
*/
@Override
public <T> void executeAsync(Call call, ApiCallback<T> callback) {
apiClient().executeAsync(call, callback);
}
/**
* Execute async.
*
* @param <T> the generic type
* @param call the call
* @param returnType the return type
* @param callback the callback
* @see ApiClient#executeAsync(okhttp3.Call, java.lang.reflect.Type, io.kubernetes.client.openapi.ApiCallback)
*/
@Override
public <T> void executeAsync(Call call, Type returnType,
ApiCallback<T> callback) {
apiClient().executeAsync(call, returnType, callback);
}
/**
* Handle response.
*
* @param <T> the generic type
* @param response the response
* @param returnType the return type
* @return the t
* @throws ApiException the api exception
* @see ApiClient#handleResponse(okhttp3.Response, java.lang.reflect.Type)
*/
@Override
public <T> T handleResponse(Response response, Type returnType)
throws ApiException {
return apiClient().handleResponse(response, returnType);
}
/**
* Builds the call.
*
* @param path the path
* @param method the method
* @param queryParams the query params
* @param collectionQueryParams the collection query params
* @param body the body
* @param headerParams the header params
* @param cookieParams the cookie params
* @param formParams the form params
* @param authNames the auth names
* @param callback the callback
* @return the call
* @throws ApiException the api exception
* @see ApiClient#buildCall(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.Object, java.util.Map, java.util.Map, java.util.Map, java.lang.String[], io.kubernetes.client.openapi.ApiCallback)
*/
@SuppressWarnings({ "rawtypes" })
@Override
public Call buildCall(String path, String method, List<Pair> queryParams,
List<Pair> collectionQueryParams, Object body,
Map<String, String> headerParams, Map<String, String> cookieParams,
Map<String, Object> formParams, String[] authNames,
ApiCallback callback) throws ApiException {
return apiClient().buildCall(path, method, queryParams,
collectionQueryParams, body, headerParams, cookieParams, formParams,
authNames, callback);
}
/**
* Builds the request.
*
* @param path the path
* @param method the method
* @param queryParams the query params
* @param collectionQueryParams the collection query params
* @param body the body
* @param headerParams the header params
* @param cookieParams the cookie params
* @param formParams the form params
* @param authNames the auth names
* @param callback the callback
* @return the request
* @throws ApiException the api exception
* @see ApiClient#buildRequest(java.lang.String, java.lang.String, java.util.List, java.util.List, java.lang.Object, java.util.Map, java.util.Map, java.util.Map, java.lang.String[], io.kubernetes.client.openapi.ApiCallback)
*/
@SuppressWarnings({ "rawtypes" })
@Override
public Request buildRequest(String path, String method,
List<Pair> queryParams, List<Pair> collectionQueryParams,
Object body, Map<String, String> headerParams,
Map<String, String> cookieParams, Map<String, Object> formParams,
String[] authNames, ApiCallback callback) throws ApiException {
return apiClient().buildRequest(path, method, queryParams,
collectionQueryParams, body, headerParams, cookieParams, formParams,
authNames, callback);
}
/**
* Builds the url.
*
* @param path the path
* @param queryParams the query params
* @param collectionQueryParams the collection query params
* @return the string
* @see ApiClient#buildUrl(java.lang.String, java.util.List, java.util.List)
*/
@Override
public String buildUrl(String path, List<Pair> queryParams,
List<Pair> collectionQueryParams) {
return apiClient().buildUrl(path, queryParams, collectionQueryParams);
}
/**
* Process header params.
*
* @param headerParams the header params
* @param reqBuilder the req builder
* @see ApiClient#processHeaderParams(java.util.Map, okhttp3.Request.Builder)
*/
@Override
public void processHeaderParams(Map<String, String> headerParams,
Builder reqBuilder) {
apiClient().processHeaderParams(headerParams, reqBuilder);
}
/**
* Process cookie params.
*
* @param cookieParams the cookie params
* @param reqBuilder the req builder
* @see ApiClient#processCookieParams(java.util.Map, okhttp3.Request.Builder)
*/
@Override
public void processCookieParams(Map<String, String> cookieParams,
Builder reqBuilder) {
apiClient().processCookieParams(cookieParams, reqBuilder);
}
/**
* Update params for auth.
*
* @param authNames the auth names
* @param queryParams the query params
* @param headerParams the header params
* @param cookieParams the cookie params
* @see ApiClient#updateParamsForAuth(java.lang.String[], java.util.List, java.util.Map, java.util.Map)
*/
@Override
public void updateParamsForAuth(String[] authNames, List<Pair> queryParams,
Map<String, String> headerParams,
Map<String, String> cookieParams) {
apiClient().updateParamsForAuth(authNames, queryParams, headerParams,
cookieParams);
}
/**
* Builds the request body form encoding.
*
* @param formParams the form params
* @return the request body
* @see ApiClient#buildRequestBodyFormEncoding(java.util.Map)
*/
@Override
public RequestBody
buildRequestBodyFormEncoding(Map<String, Object> formParams) {
return apiClient().buildRequestBodyFormEncoding(formParams);
}
/**
* Builds the request body multipart.
*
* @param formParams the form params
* @return the request body
* @see ApiClient#buildRequestBodyMultipart(java.util.Map)
*/
@Override
public RequestBody
buildRequestBodyMultipart(Map<String, Object> formParams) {
return apiClient().buildRequestBodyMultipart(formParams);
}
/**
* Guess content type from file.
*
* @param file the file
* @return the string
* @see ApiClient#guessContentTypeFromFile(java.io.File)
*/
@Override
public String guessContentTypeFromFile(File file) {
return apiClient().guessContentTypeFromFile(file);
}
}

View file

@ -1,396 +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 io.kubernetes.client.Discovery.APIResource;
import io.kubernetes.client.apimachinery.GroupVersionKind;
import io.kubernetes.client.common.KubernetesListObject;
import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.util.Strings;
import io.kubernetes.client.util.generic.GenericKubernetesApi;
import io.kubernetes.client.util.generic.options.GetOptions;
import io.kubernetes.client.util.generic.options.ListOptions;
import io.kubernetes.client.util.generic.options.PatchOptions;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
/**
* A stub for cluster scoped objects. This stub provides the
* functions common to all Kubernetes objects, but uses variables
* for all types. This class should be used as base class only.
*
* @param <O> the generic type
* @param <L> the generic type
*/
@SuppressWarnings({ "PMD.CouplingBetweenObjects" })
public class K8sClusterGenericStub<O extends KubernetesObject,
L extends KubernetesListObject> {
protected final K8sClient client;
private final GenericKubernetesApi<O, L> api;
protected final APIResource context;
protected final String name;
/**
* Instantiates a new stub for the object specified. If the object
* exists in the context specified, the version (see
* {@link #version()} is bound to the existing object's version.
* Else the stub is dangling with the version set to the context's
* preferred version.
*
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the context
* @param name the name
*/
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
protected K8sClusterGenericStub(Class<O> objectClass,
Class<L> objectListClass, K8sClient client, APIResource context,
String name) {
this.client = client;
this.name = name;
// Bind version
var foundVersion = context.getPreferredVersion();
GenericKubernetesApi<O, L> testApi = null;
GetOptions mdOpts
= new GetOptions().isPartialObjectMetadataRequest(true);
for (var version : candidateVersions(context)) {
testApi = new GenericKubernetesApi<>(objectClass, objectListClass,
context.getGroup(), version, context.getResourcePlural(),
client);
if (testApi.get(name, mdOpts).isSuccess()) {
foundVersion = version;
break;
}
}
if (foundVersion.equals(context.getPreferredVersion())) {
this.context = context;
} else {
this.context = K8s.preferred(context, foundVersion);
}
api = Optional.ofNullable(testApi)
.orElseGet(() -> new GenericKubernetesApi<>(objectClass,
objectListClass, group(), version(), plural(), client));
}
/**
* Gets the context.
*
* @return the context
*/
public APIResource context() {
return context;
}
/**
* Gets the group.
*
* @return the group
*/
public String group() {
return context.getGroup();
}
/**
* Gets the version.
*
* @return the version
*/
public String version() {
return context.getPreferredVersion();
}
/**
* Gets the kind.
*
* @return the kind
*/
public String kind() {
return context.getKind();
}
/**
* Gets the plural.
*
* @return the plural
*/
public String plural() {
return context.getResourcePlural();
}
/**
* Gets the name.
*
* @return the name
*/
public String name() {
return name;
}
/**
* Delete the Kubernetes object.
*
* @throws ApiException the API exception
*/
public void delete() throws ApiException {
var result = api.delete(name);
if (result.isSuccess()
|| result.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return;
}
result.throwsApiException();
}
/**
* Retrieves and returns the current state of the object.
*
* @return the object's state
* @throws ApiException the api exception
*/
public Optional<O> model() throws ApiException {
return K8s.optional(api.get(name));
}
/**
* Updates the object's status.
*
* @param object the current state of the object (passed to `status`)
* @param status function that returns the new status
* @return the updated model or empty if not successful
* @throws ApiException the api exception
*/
public Optional<O> updateStatus(O object,
Function<O, Object> status) throws ApiException {
return K8s.optional(api.updateStatus(object, status));
}
/**
* Updates the status.
*
* @param status the status
* @return the kubernetes api response
* the updated model or empty if not successful
* @throws ApiException the api exception
*/
public Optional<O> updateStatus(Function<O, Object> status)
throws ApiException {
return updateStatus(api.get(name).throwsApiException().getObject(),
status);
}
/**
* Patch the object.
*
* @param patchType the patch type
* @param patch the patch
* @param options the options
* @return the kubernetes api response
* @throws ApiException the api exception
*/
public Optional<O> patch(String patchType, V1Patch patch,
PatchOptions options) throws ApiException {
return K8s
.optional(api.patch(name, patchType, patch, options));
}
/**
* Patch the object using default options.
*
* @param patchType the patch type
* @param patch the patch
* @return the kubernetes api response
* @throws ApiException the api exception
*/
public Optional<O>
patch(String patchType, V1Patch patch) throws ApiException {
PatchOptions opts = new PatchOptions();
return patch(patchType, patch, opts);
}
/**
* A supplier for generic stubs.
*
* @param <O> the object type
* @param <L> the object list type
* @param <R> the result type
*/
@FunctionalInterface
public interface GenericSupplier<O extends KubernetesObject,
L extends KubernetesListObject,
R extends K8sClusterGenericStub<O, L>> {
/**
* Gets a new stub.
*
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the API resource
* @param name the name
* @return the result
*/
R get(Class<O> objectClass, Class<L> objectListClass, K8sClient client,
APIResource context, String name);
}
@Override
@SuppressWarnings("PMD.UseLocaleWithCaseConversions")
public String toString() {
return (Strings.isNullOrEmpty(group()) ? "" : group() + "/")
+ version().toUpperCase() + kind() + " " + name;
}
/**
* Get an object stub. If the version in parameter
* `gvk` is an empty string, the stub refers to the first object
* found with matching group and kind.
*
* @param <O> the object type
* @param <L> the object list type
* @param <R> the stub type
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param gvk the group, version and kind
* @param name the name
* @param provider the provider
* @return the stub if the object exists
* @throws ApiException the api exception
*/
public static <O extends KubernetesObject, L extends KubernetesListObject,
R extends K8sClusterGenericStub<O, L>>
R get(Class<O> objectClass, Class<L> objectListClass,
K8sClient client, GroupVersionKind gvk, String name,
GenericSupplier<O, L, R> provider) throws ApiException {
var context = K8s.context(client, gvk.getGroup(), gvk.getVersion(),
gvk.getKind());
if (context.isEmpty()) {
throw new ApiException("No known API for " + gvk.getGroup()
+ "/" + gvk.getVersion() + " " + gvk.getKind());
}
return provider.get(objectClass, objectListClass, client, context.get(),
name);
}
/**
* Get an object stub.
*
* @param <O> the object type
* @param <L> the object list type
* @param <R> the stub type
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the context
* @param name the name
* @param provider the provider
* @return the stub if the object exists
* @throws ApiException the api exception
*/
public static <O extends KubernetesObject, L extends KubernetesListObject,
R extends K8sClusterGenericStub<O, L>>
R get(Class<O> objectClass, Class<L> objectListClass,
K8sClient client, APIResource context, String name,
GenericSupplier<O, L, R> provider) {
return provider.get(objectClass, objectListClass, client, context,
name);
}
/**
* Get an object stub for a newly created object.
*
* @param <O> the object type
* @param <L> the object list type
* @param <R> the stub type
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the context
* @param model the model
* @param provider the provider
* @return the stub if the object exists
* @throws ApiException the api exception
*/
public static <O extends KubernetesObject, L extends KubernetesListObject,
R extends K8sClusterGenericStub<O, L>>
R create(Class<O> objectClass, Class<L> objectListClass,
K8sClient client, APIResource context, O model,
GenericSupplier<O, L, R> provider) throws ApiException {
var api = new GenericKubernetesApi<>(objectClass, objectListClass,
context.getGroup(), context.getPreferredVersion(),
context.getResourcePlural(), client);
api.create(model).throwsApiException();
return provider.get(objectClass, objectListClass, client,
context, model.getMetadata().getName());
}
/**
* Get the stubs for the objects that match
* the criteria from the given options.
*
* @param <O> the object type
* @param <L> the object list type
* @param <R> the stub type
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the context
* @param options the options
* @param provider the provider
* @return the collection
* @throws ApiException the api exception
*/
public static <O extends KubernetesObject, L extends KubernetesListObject,
R extends K8sClusterGenericStub<O, L>>
Collection<R> list(Class<O> objectClass, Class<L> objectListClass,
K8sClient client, APIResource context,
ListOptions options, GenericSupplier<O, L, R> provider)
throws ApiException {
var result = new ArrayList<R>();
for (var version : candidateVersions(context)) {
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
var api = new GenericKubernetesApi<>(objectClass, objectListClass,
context.getGroup(), version, context.getResourcePlural(),
client);
var objs = api.list(options).throwsApiException();
for (var item : objs.getObject().getItems()) {
result.add(provider.get(objectClass, objectListClass, client,
context, item.getMetadata().getName()));
}
}
return result;
}
private static List<String> candidateVersions(APIResource context) {
var result = new LinkedList<>(context.getVersions());
result.remove(context.getPreferredVersion());
result.add(0, context.getPreferredVersion());
return result;
}
}

View file

@ -1,113 +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 io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
/**
* Represents a Kubernetes object using a JSON data structure.
* Some information that is common to all Kubernetes objects,
* notably the metadata, is made available through the methods
* defined by {@link KubernetesObject}.
*/
public class K8sDynamicModel implements KubernetesObject {
private final V1ObjectMeta metadata;
private final JsonObject data;
/**
* Instantiates a new model from the JSON representation.
*
* @param delegate the gson instance to use for extracting structured data
* @param json the JSON
*/
public K8sDynamicModel(Gson delegate, JsonObject json) {
this.data = json;
metadata = delegate.fromJson(data.get("metadata"), V1ObjectMeta.class);
}
@Override
public String getApiVersion() {
return apiVersion();
}
/**
* Gets the API version. (Abbreviated method name for convenience.)
*
* @return the API version
*/
public String apiVersion() {
return data.get("apiVersion").getAsString();
}
@Override
public String getKind() {
return kind();
}
/**
* Gets the kind. (Abbreviated method name for convenience.)
*
* @return the kind
*/
public String kind() {
return data.get("kind").getAsString();
}
@Override
public V1ObjectMeta getMetadata() {
return metadata;
}
/**
* Gets the metadata. (Abbreviated method name for convenience.)
*
* @return the metadata
*/
public V1ObjectMeta metadata() {
return metadata;
}
/**
* Gets the data.
*
* @return the data
*/
public JsonObject data() {
return data;
}
/**
* Convenience method for getting the status.
*
* @return the JSON object describing the status
*/
public JsonObject statusJson() {
return data.getAsJsonObject("status");
}
@Override
public String toString() {
return data.toString();
}
}

View file

@ -1,44 +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 io.kubernetes.client.common.KubernetesListObject;
/**
* Represents a list of Kubernetes objects each of which is
* represented using a JSON data structure.
* Some information that is common to all Kubernetes objects,
* notably the metadata, is made available through the methods
* defined by {@link KubernetesListObject}.
*/
public class K8sDynamicModels extends K8sDynamicModelsBase<K8sDynamicModel> {
/**
* Initialize the object list using the given JSON data.
*
* @param delegate the gson instance to use for extracting structured data
* @param data the data
*/
public K8sDynamicModels(Gson delegate, JsonObject data) {
super(K8sDynamicModel.class, delegate, data);
}
}

View file

@ -1,174 +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.JsonElement;
import com.google.gson.JsonObject;
import io.kubernetes.client.common.KubernetesListObject;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.openapi.models.V1ListMeta;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Represents a list of Kubernetes objects each of which is
* represented using a JSON data structure.
* Some information that is common to all Kubernetes objects,
* notably the metadata, is made available through the methods
* defined by {@link KubernetesListObject}.
*/
public class K8sDynamicModelsBase<T extends K8sDynamicModel>
implements KubernetesListObject {
private final JsonObject data;
private final V1ListMeta metadata;
private final List<T> items;
/**
* Initialize the object list using the given JSON data.
*
* @param itemClass the item class
* @param delegate the gson instance to use for extracting structured data
* @param data the data
*/
public K8sDynamicModelsBase(Class<T> itemClass, Gson delegate,
JsonObject data) {
this.data = data;
metadata = delegate.fromJson(data.get("metadata"), V1ListMeta.class);
items = new ArrayList<>();
for (JsonElement e : data.get("items").getAsJsonArray()) {
try {
items.add(itemClass.getConstructor(Gson.class, JsonObject.class)
.newInstance(delegate, e.getAsJsonObject()));
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException exc) {
throw new IllegalArgumentException(exc);
}
}
}
@Override
public String getApiVersion() {
return apiVersion();
}
/**
* Gets the API version. (Abbreviated method name for convenience.)
*
* @return the API version
*/
public String apiVersion() {
return data.get("apiVersion").getAsString();
}
@Override
public String getKind() {
return kind();
}
/**
* Gets the kind. (Abbreviated method name for convenience.)
*
* @return the kind
*/
public String kind() {
return data.get("kind").getAsString();
}
@Override
public V1ListMeta getMetadata() {
return metadata;
}
/**
* Gets the metadata. (Abbreviated method name for convenience.)
*
* @return the metadata
*/
public V1ListMeta metadata() {
return metadata;
}
/**
* Returns the JSON representation of this object.
*
* @return the JOSN representation
*/
public JsonObject data() {
return data;
}
@Override
public List<T> getItems() {
return items;
}
/**
* Sets the api version.
*
* @param apiVersion the new api version
*/
public void setApiVersion(String apiVersion) {
data.addProperty("apiVersion", apiVersion);
}
/**
* Sets the kind.
*
* @param kind the new kind
*/
public void setKind(String kind) {
data.addProperty("kind", kind);
}
/**
* Sets the metadata.
*
* @param objectMeta the new metadata
*/
public void setMetadata(V1ListMeta objectMeta) {
data.add("metadata",
Configuration.getDefaultApiClient().getJSON().getGson()
.toJsonTree(objectMeta));
}
@Override
public int hashCode() {
return Objects.hash(data);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
K8sDynamicModelsBase<?> other = (K8sDynamicModelsBase<?>) obj;
return Objects.equals(data, other.data);
}
}

View file

@ -1,152 +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 io.kubernetes.client.Discovery.APIResource;
import io.kubernetes.client.apimachinery.GroupVersionKind;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.util.generic.options.ListOptions;
import java.io.Reader;
import java.util.Collection;
/**
* A stub for namespaced custom objects. It uses a dynamic model
* (see {@link K8sDynamicModel}) for representing the object's
* state and can therefore be used for any kind of object, especially
* custom objects.
*/
public class K8sDynamicStub
extends K8sDynamicStubBase<K8sDynamicModel, K8sDynamicModels> {
private static DynamicTypeAdapterFactory<K8sDynamicModel,
K8sDynamicModels> taf = new K8sDynamicModelTypeAdapterFactory();
/**
* Instantiates a new dynamic stub.
*
* @param client the client
* @param context the context
* @param namespace the namespace
* @param name the name
*/
public K8sDynamicStub(K8sClient client,
APIResource context, String namespace, String name) {
super(K8sDynamicModel.class, K8sDynamicModels.class, taf, client,
context, namespace, name);
}
/**
* Get a dynamic object stub. If the version in parameter
* `gvk` is an empty string, the stub refers to the first object with
* matching group and kind.
*
* @param client the client
* @param gvk the group, version and kind
* @param namespace the namespace
* @param name the name
* @return the stub if the object exists
* @throws ApiException the api exception
*/
public static K8sDynamicStub get(K8sClient client,
GroupVersionKind gvk, String namespace, String name)
throws ApiException {
return new K8sDynamicStub(client, apiResource(client, gvk), namespace,
name);
}
/**
* Get a dynamic object stub.
*
* @param client the client
* @param context the context
* @param namespace the namespace
* @param name the name
* @return the stub if the object exists
* @throws ApiException the api exception
*/
public static K8sDynamicStub get(K8sClient client,
APIResource context, String namespace, String name) {
return new K8sDynamicStub(client, context, namespace, name);
}
/**
* Creates a stub from yaml.
*
* @param client the client
* @param context the context
* @param yaml the yaml
* @return the k 8 s dynamic stub
* @throws ApiException the api exception
*/
public static K8sDynamicStub createFromYaml(K8sClient client,
APIResource context, Reader yaml) throws ApiException {
var model = new K8sDynamicModel(client.getJSON().getGson(),
K8s.yamlToJson(client, yaml));
return K8sGenericStub.create(K8sDynamicModel.class,
K8sDynamicModels.class, client, context, model,
(c, ns, n) -> new K8sDynamicStub(c, context, ns, n));
}
/**
* Get the stubs for the objects in the given namespace that match
* the criteria from the given options.
*
* @param client the client
* @param namespace the namespace
* @param options the options
* @return the collection
* @throws ApiException the api exception
*/
public static Collection<K8sDynamicStub> list(K8sClient client,
APIResource context, String namespace, ListOptions options)
throws ApiException {
return K8sGenericStub.list(K8sDynamicModel.class,
K8sDynamicModels.class, client, context, namespace, options,
(c, ns, n) -> new K8sDynamicStub(c, context, ns, n));
}
/**
* Get the stubs for the objects in the given namespace.
*
* @param client the client
* @param namespace the namespace
* @return the collection
* @throws ApiException the api exception
*/
public static Collection<K8sDynamicStub> list(K8sClient client,
APIResource context, String namespace)
throws ApiException {
return list(client, context, namespace, new ListOptions());
}
/**
* A factory for creating K8sDynamicModel(s) objects.
*/
public static class K8sDynamicModelTypeAdapterFactory extends
DynamicTypeAdapterFactory<K8sDynamicModel, K8sDynamicModels> {
/**
* Instantiates a new dynamic model type adapter factory.
*/
public K8sDynamicModelTypeAdapterFactory() {
super(K8sDynamicModel.class, K8sDynamicModels.class);
}
}
}

View file

@ -1,49 +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 io.kubernetes.client.Discovery.APIResource;
/**
* A stub for namespaced custom objects. It uses a dynamic model
* (see {@link K8sDynamicModel}) for representing the object's
* state and can therefore be used for any kind of object, especially
* custom objects.
*/
public abstract class K8sDynamicStubBase<O extends K8sDynamicModel,
L extends K8sDynamicModelsBase<O>> extends K8sGenericStub<O, L> {
/**
* Instantiates a new dynamic stub.
*
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the context
* @param namespace the namespace
* @param name the name
*/
public K8sDynamicStubBase(Class<O> objectClass,
Class<L> objectListClass, DynamicTypeAdapterFactory<O, L> taf,
K8sClient client, APIResource context, String namespace,
String name) {
super(objectClass, objectListClass, client, context, namespace, name);
taf.register(client);
}
}

View file

@ -1,474 +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 io.kubernetes.client.Discovery.APIResource;
import io.kubernetes.client.apimachinery.GroupVersionKind;
import io.kubernetes.client.common.KubernetesListObject;
import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.util.Strings;
import io.kubernetes.client.util.generic.GenericKubernetesApi;
import io.kubernetes.client.util.generic.KubernetesApiResponse;
import io.kubernetes.client.util.generic.dynamic.DynamicKubernetesObject;
import io.kubernetes.client.util.generic.options.GetOptions;
import io.kubernetes.client.util.generic.options.ListOptions;
import io.kubernetes.client.util.generic.options.PatchOptions;
import io.kubernetes.client.util.generic.options.UpdateOptions;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
/**
* A stub for namespaced custom objects. This stub provides the
* functions common to all Kubernetes objects, but uses variables
* for all types. This class should be used as base class only.
*
* @param <O> the generic type
* @param <L> the generic type
*/
@SuppressWarnings({ "PMD.TooManyMethods" })
public class K8sGenericStub<O extends KubernetesObject,
L extends KubernetesListObject> {
protected final K8sClient client;
private final GenericKubernetesApi<O, L> api;
protected final APIResource context;
protected final String namespace;
protected final String name;
/**
* Instantiates a new stub for the object specified. If the object
* exists in the context specified, the version (see
* {@link #version()} is bound to the existing object's version.
* Else the stub is dangling with the version set to the context's
* preferred version.
*
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the context
* @param namespace the namespace
* @param name the name
*/
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
protected K8sGenericStub(Class<O> objectClass, Class<L> objectListClass,
K8sClient client, APIResource context, String namespace,
String name) {
this.client = client;
this.namespace = namespace;
this.name = name;
// Bind version
var foundVersion = context.getPreferredVersion();
GenericKubernetesApi<O, L> testApi = null;
GetOptions mdOpts
= new GetOptions().isPartialObjectMetadataRequest(true);
for (var version : candidateVersions(context)) {
testApi = new GenericKubernetesApi<>(objectClass, objectListClass,
context.getGroup(), version, context.getResourcePlural(),
client);
if (testApi.get(namespace, name, mdOpts)
.isSuccess()) {
foundVersion = version;
break;
}
}
if (foundVersion.equals(context.getPreferredVersion())) {
this.context = context;
} else {
this.context = K8s.preferred(context, foundVersion);
}
api = Optional.ofNullable(testApi)
.orElseGet(() -> new GenericKubernetesApi<>(objectClass,
objectListClass, group(), version(), plural(), client));
}
/**
* Gets the context.
*
* @return the context
*/
public APIResource context() {
return context;
}
/**
* Gets the group.
*
* @return the group
*/
public String group() {
return context.getGroup();
}
/**
* Gets the version.
*
* @return the version
*/
public String version() {
return context.getPreferredVersion();
}
/**
* Gets the kind.
*
* @return the kind
*/
public String kind() {
return context.getKind();
}
/**
* Gets the plural.
*
* @return the plural
*/
public String plural() {
return context.getResourcePlural();
}
/**
* Gets the namespace.
*
* @return the namespace
*/
public String namespace() {
return namespace;
}
/**
* Gets the name.
*
* @return the name
*/
public String name() {
return name;
}
/**
* Delete the Kubernetes object.
*
* @throws ApiException the API exception
*/
public void delete() throws ApiException {
var result = api.delete(namespace, name);
if (result.isSuccess()
|| result.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return;
}
result.throwsApiException();
}
/**
* Retrieves and returns the current state of the object.
*
* @return the object's state
* @throws ApiException the api exception
*/
public Optional<O> model() throws ApiException {
return K8s.optional(api.get(namespace, name));
}
/**
* Updates the object's status. Does not retry in case of conflict.
*
* @param object the current state of the object (passed to `status`)
* @param updater function that returns the new status
* @return the updated model or empty if the object was not found
* @throws ApiException the api exception
*/
public Optional<O> updateStatus(O object, Function<O, Object> updater)
throws ApiException {
return K8s.optional(api.updateStatus(object, updater));
}
/**
* 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 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
* the updated model or empty if not successful
* @throws ApiException the api exception
*/
public Optional<O> updateStatus(Function<O, Object> updater, O current)
throws ApiException {
return updateStatus(updater, current, 16);
}
/**
* 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);
}
/**
* Patch the object.
*
* @param patchType the patch type
* @param patch the patch
* @param options the options
* @return the kubernetes api response if successful
* @throws ApiException the api exception
*/
public Optional<O> patch(String patchType, V1Patch patch,
PatchOptions options) throws ApiException {
return K8s
.optional(api.patch(namespace, name, patchType, patch, options)
.throwsApiException());
}
/**
* Patch the object using default options.
*
* @param patchType the patch type
* @param patch the patch
* @return the kubernetes api response if successful
* @throws ApiException the api exception
*/
public Optional<O>
patch(String patchType, V1Patch patch) throws ApiException {
PatchOptions opts = new PatchOptions();
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.
*
* @param object the object
* @return the kubernetes api response
* @throws ApiException the api exception
*/
public KubernetesApiResponse<O> update(O object) throws ApiException {
return api.update(object).throwsApiException();
}
/**
* Update the object.
*
* @param object the object
* @param options the options
* @return the kubernetes api response
* @throws ApiException the api exception
*/
public KubernetesApiResponse<O> update(O object, UpdateOptions options)
throws ApiException {
return api.update(object, options).throwsApiException();
}
/**
* A supplier for generic stubs.
*
* @param <O> the object type
* @param <L> the object list type
* @param <R> the result type
*/
@FunctionalInterface
public interface GenericSupplier<O extends KubernetesObject,
L extends KubernetesListObject, R extends K8sGenericStub<O, L>> {
/**
* Gets a new stub.
*
* @param client the client
* @param namespace the namespace
* @param name the name
* @return the result
*/
R get(K8sClient client, String namespace, String name);
}
@Override
@SuppressWarnings("PMD.UseLocaleWithCaseConversions")
public String toString() {
return (Strings.isNullOrEmpty(group()) ? "" : group() + "/")
+ version().toUpperCase() + kind() + " " + namespace + ":" + name;
}
/**
* Get a namespaced object stub for a newly created object.
*
* @param <O> the object type
* @param <L> the object list type
* @param <R> the stub type
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the context
* @param model the model
* @param provider the provider
* @return the stub if the object exists
* @throws ApiException the api exception
*/
public static <O extends KubernetesObject, L extends KubernetesListObject,
R extends K8sGenericStub<O, L>>
R create(Class<O> objectClass, Class<L> objectListClass,
K8sClient client, APIResource context, O model,
GenericSupplier<O, L, R> provider) throws ApiException {
var api = new GenericKubernetesApi<>(objectClass, objectListClass,
context.getGroup(), context.getPreferredVersion(),
context.getResourcePlural(), client);
api.create(model).throwsApiException();
return provider.get(client, model.getMetadata().getNamespace(),
model.getMetadata().getName());
}
/**
* Get the stubs for the objects in the given namespace that match
* the criteria from the given options.
*
* @param <O> the object type
* @param <L> the object list type
* @param <R> the stub type
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the context
* @param namespace the namespace
* @param options the options
* @param provider the provider
* @return the collection
* @throws ApiException the api exception
*/
public static <O extends KubernetesObject, L extends KubernetesListObject,
R extends K8sGenericStub<O, L>>
Collection<R> list(Class<O> objectClass, Class<L> objectListClass,
K8sClient client, APIResource context, String namespace,
ListOptions options, GenericSupplier<O, L, R> provider)
throws ApiException {
var result = new ArrayList<R>();
for (var version : candidateVersions(context)) {
@SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
var api = new GenericKubernetesApi<>(objectClass, objectListClass,
context.getGroup(), version, context.getResourcePlural(),
client);
var objs = api.list(namespace, options).throwsApiException();
for (var item : objs.getObject().getItems()) {
result.add(provider.get(client, namespace,
item.getMetadata().getName()));
}
}
return result;
}
private static List<String> candidateVersions(APIResource context) {
var result = new LinkedList<>(context.getVersions());
result.remove(context.getPreferredVersion());
result.add(0, context.getPreferredVersion());
return result;
}
/**
* Api resource.
*
* @param client the client
* @param gvk the gvk
* @return the API resource
* @throws ApiException the api exception
*/
public static APIResource apiResource(K8sClient client,
GroupVersionKind gvk) throws ApiException {
var context = K8s.context(client, gvk.getGroup(), gvk.getVersion(),
gvk.getKind());
if (context.isEmpty()) {
throw new ApiException("No known API for " + gvk.getGroup()
+ "/" + gvk.getVersion() + " " + gvk.getKind());
}
return context.get();
}
}

View file

@ -1,237 +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 io.kubernetes.client.Discovery.APIResource;
import io.kubernetes.client.common.KubernetesListObject;
import io.kubernetes.client.common.KubernetesObject;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.util.Watch.Response;
import io.kubernetes.client.util.generic.GenericKubernetesApi;
import io.kubernetes.client.util.generic.options.ListOptions;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jgrapes.core.Components;
/**
* An observer that watches namespaced resources in a given context and
* invokes a handler on changes.
*
* @param <O> the object type for the context
* @param <L> the object list type for the context
*/
public class K8sObserver<O extends KubernetesObject,
L extends KubernetesListObject> {
/**
* The type of change reported by {@link Response} as enum.
*/
public enum ResponseType {
ADDED, MODIFIED, DELETED
}
protected final Logger logger = Logger.getLogger(getClass().getName());
protected final K8sClient client;
protected final GenericKubernetesApi<O, L> api;
protected final APIResource context;
protected final String namespace;
protected final ListOptions options;
protected final Thread thread;
protected BiConsumer<K8sClient, Response<O>> handler;
protected BiConsumer<K8sObserver<O, L>, Throwable> onTerminated;
/**
* Create and start a new observer for objects in the given context
* (using preferred version) and namespace with the given options.
*
* @param objectClass the object class
* @param objectListClass the object list class
* @param client the client
* @param context the context
* @param namespace the namespace
* @param options the options
*/
@SuppressWarnings({ "PMD.AvoidCatchingThrowable",
"PMD.CognitiveComplexity", "PMD.AvoidCatchingGenericException" })
public K8sObserver(Class<O> objectClass, Class<L> objectListClass,
K8sClient client, APIResource context, String namespace,
ListOptions options) {
this.client = client;
this.context = context;
this.namespace = namespace;
this.options = options;
api = new GenericKubernetesApi<>(objectClass, objectListClass,
context.getGroup(), context.getPreferredVersion(),
context.getResourcePlural(), client);
thread = (Components.useVirtualThreads() ? Thread.ofVirtual()
: Thread.ofPlatform()).unstarted(() -> {
try {
logger.fine(() -> "Observing " + context.getResourcePlural()
+ " (" + context.getPreferredVersion() + ")"
+ Optional.ofNullable(options.getLabelSelector())
.map(ls -> " with labels " + ls).orElse("")
+ " in " + namespace);
// Watch sometimes terminates without apparent reason.
while (!Thread.currentThread().isInterrupted()) {
Instant startedAt = Instant.now();
try {
var changed
= api.watch(namespace, options).iterator();
while (changed.hasNext()) {
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);
}
}
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);
}
}
});
}
@SuppressWarnings("PMD.AvoidLiteralsInIfCondition")
private void delayRestart(Instant started) {
var runningFor = Duration
.between(started, Instant.now()).toMillis();
if (runningFor < 5000) {
logger.log(Level.FINE, () -> "Waiting... ");
try {
Thread.sleep(5000 - runningFor);
} catch (InterruptedException e1) { // NOPMD
// Retry
}
logger.log(Level.FINE, () -> "Retrying");
}
}
/**
* Sets the handler.
*
* @param handler the handler
* @return the observer
*/
public K8sObserver<O, L>
handler(BiConsumer<K8sClient, Response<O>> handler) {
this.handler = handler;
return this;
}
/**
* Sets a function to invoke if the observer terminates. First argument
* is this observer, the second is the throwable that caused the
* abnormal termination or `null` if the observer was terminated
* by {@link #stop()}.
*
* @param onTerminated the on terminated
* @return the observer
*/
public K8sObserver<O, L> onTerminated(
BiConsumer<K8sObserver<O, L>, Throwable> onTerminated) {
this.onTerminated = onTerminated;
return this;
}
/**
* Start the observer.
*
* @return the observer
*/
public K8sObserver<O, L> start() {
if (handler == null) {
throw new IllegalStateException("No handler defined");
}
thread.start();
return this;
}
/**
* Stops the observer.
*
* @return the observer
*/
public K8sObserver<O, L> stop() {
thread.interrupt();
return this;
}
/**
* Returns the client.
*
* @return the client
*/
public K8sClient client() {
return client;
}
/**
* Returns the context.
*
* @return the context
*/
public APIResource context() {
return context;
}
/**
* Returns the observed namespace.
*
* @return the namespace
*/
public String getNamespace() {
return namespace;
}
/**
* Returns the options for object selection.
*
* @return the list options
*/
public ListOptions options() {
return options;
}
@Override
public String toString() {
return "Observer for " + K8s.toString(context) + " " + namespace;
}
}

View file

@ -1,60 +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 io.kubernetes.client.Discovery.APIResource;
import io.kubernetes.client.openapi.models.V1ConfigMap;
import io.kubernetes.client.openapi.models.V1ConfigMapList;
import java.util.List;
/**
* A stub for config maps (v1).
*/
public class K8sV1ConfigMapStub
extends K8sGenericStub<V1ConfigMap, V1ConfigMapList> {
public static final APIResource CONTEXT = new APIResource("", List.of("v1"),
"v1", "ConfigMap", true, "configmaps", "configmap");
/**
* Instantiates a new stub.
*
* @param client the client
* @param namespace the namespace
* @param name the name
*/
protected K8sV1ConfigMapStub(K8sClient client, String namespace,
String name) {
super(V1ConfigMap.class, V1ConfigMapList.class, client,
CONTEXT, namespace, name);
}
/**
* Gets the stub for the given namespace and name.
*
* @param client the client
* @param namespace the namespace
* @param name the name
* @return the config map stub
*/
public static K8sV1ConfigMapStub get(K8sClient client, String namespace,
String name) {
return new K8sV1ConfigMapStub(client, namespace, name);
}
}

View file

@ -1,78 +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 io.kubernetes.client.Discovery.APIResource;
import io.kubernetes.client.custom.V1Patch;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1Deployment;
import io.kubernetes.client.openapi.models.V1DeploymentList;
import java.util.List;
import java.util.Optional;
/**
* A stub for pods (v1).
*/
public class K8sV1DeploymentStub
extends K8sGenericStub<V1Deployment, V1DeploymentList> {
/** The deployment's context. */
public static final APIResource CONTEXT = new APIResource("apps",
List.of("v1"), "v1", "Pod", true, "deployments", "deployment");
/**
* Instantiates a new stub.
*
* @param client the client
* @param namespace the namespace
* @param name the name
*/
protected K8sV1DeploymentStub(K8sClient client, String namespace,
String name) {
super(V1Deployment.class, V1DeploymentList.class, client,
CONTEXT, namespace, name);
}
/**
* Scales the deployment.
*
* @param replicas the replicas
* @return the new model or empty if not successful
* @throws ApiException the API exception
*/
public Optional<V1Deployment> scale(int replicas) throws ApiException {
return patch(V1Patch.PATCH_FORMAT_JSON_PATCH,
new V1Patch("[{\"op\": \"replace\", \"path\": \"/spec/replicas"
+ "\", \"value\": " + replicas + "}]"),
client.defaultPatchOptions());
}
/**
* Gets the stub for the given namespace and name.
*
* @param client the client
* @param namespace the namespace
* @param name the name
* @return the deployment stub
*/
public static K8sV1DeploymentStub get(K8sClient client, String namespace,
String name) {
return new K8sV1DeploymentStub(client, namespace, name);
}
}

View file

@ -1,83 +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 io.kubernetes.client.Discovery.APIResource;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1Node;
import io.kubernetes.client.openapi.models.V1NodeList;
import io.kubernetes.client.util.generic.options.ListOptions;
import java.util.Collection;
import java.util.List;
/**
* A stub for nodes (v1).
*/
public class K8sV1NodeStub extends K8sClusterGenericStub<V1Node, V1NodeList> {
public static final APIResource CONTEXT = new APIResource("", List.of("v1"),
"v1", "Node", true, "nodes", "node");
/**
* Instantiates a new stub.
*
* @param client the client
* @param name the name
*/
protected K8sV1NodeStub(K8sClient client, String name) {
super(V1Node.class, V1NodeList.class, client, CONTEXT, name);
}
/**
* Gets the stub for the given name.
*
* @param client the client
* @param name the name
* @return the config map stub
*/
public static K8sV1NodeStub get(K8sClient client, String name) {
return new K8sV1NodeStub(client, name);
}
/**
* Get the stubs for the objects that match
* the criteria from the given options.
*
* @param client the client
* @param options the options
* @return the collection
* @throws ApiException the api exception
*/
public static Collection<K8sV1NodeStub> list(K8sClient client,
ListOptions options) throws ApiException {
return K8sClusterGenericStub.list(V1Node.class, V1NodeList.class,
client, CONTEXT, options, K8sV1NodeStub::getGeneric);
}
/**
* Provide {@link GenericSupplier}.
*/
@SuppressWarnings({ "PMD.UnusedFormalParameter" })
private static K8sV1NodeStub getGeneric(Class<V1Node> objectClass,
Class<V1NodeList> objectListClass, K8sClient client,
APIResource context, String name) {
return new K8sV1NodeStub(client, name);
}
}

View file

@ -1,78 +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 io.kubernetes.client.Discovery.APIResource;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.models.V1Pod;
import io.kubernetes.client.openapi.models.V1PodList;
import io.kubernetes.client.util.generic.options.ListOptions;
import java.util.Collection;
import java.util.List;
/**
* A stub for pods (v1).
*/
public class K8sV1PodStub extends K8sGenericStub<V1Pod, V1PodList> {
/** The pods' context. */
public static final APIResource CONTEXT
= new APIResource("", List.of("v1"), "v1", "Pod", true, "pods", "pod");
/**
* Instantiates a new stub.
*
* @param client the client
* @param namespace the namespace
* @param name the name
*/
protected K8sV1PodStub(K8sClient client, String namespace, String name) {
super(V1Pod.class, V1PodList.class, client, CONTEXT, namespace, name);
}
/**
* Gets the stub for the given namespace and name.
*
* @param client the client
* @param namespace the namespace
* @param name the name
* @return the kpod stub
*/
public static K8sV1PodStub get(K8sClient client, String namespace,
String name) {
return new K8sV1PodStub(client, namespace, name);
}
/**
* Get the stubs for the objects in the given namespace that match
* the criteria from the given options.
*
* @param client the client
* @param namespace the namespace
* @param options the options
* @return the collection
* @throws ApiException the api exception
*/
public static Collection<K8sV1PodStub> list(K8sClient client,
String namespace, ListOptions options) throws ApiException {
return K8sGenericStub.list(V1Pod.class, V1PodList.class, client,
CONTEXT, namespace, options, (clnt, nscp,
name) -> new K8sV1PodStub(clnt, nscp, name));
}
}

Some files were not shown because too many files have changed in this diff Show more