Compare commits

..

689 commits

Author SHA1 Message Date
c6c6358426 Fix warnings.
Some checks failed
ci/woodpecker/push/build Pipeline was successful
CodeQL / Analyze (push) Has been cancelled
Java CI with Gradle / build (push) Has been cancelled
Deploy Jekyll site to Pages / build (push) Has been cancelled
Deploy Jekyll site to Pages / deploy (push) Has been cancelled
2025-08-11 20:32:08 +02:00
470c266157 Build with woodpecker (#1)
Some checks are pending
CodeQL / Analyze (push) Waiting to run
Java CI with Gradle / build (push) Waiting to run
Deploy Jekyll site to Pages / build (push) Waiting to run
Deploy Jekyll site to Pages / deploy (push) Blocked by required conditions
ci/woodpecker/push/build Pipeline was successful
Reviewed-on: #1
Co-authored-by: Michael N. Lipp <mnl@mnl.de>
Co-committed-by: Michael N. Lipp <mnl@mnl.de>
2025-08-11 12:50:28 +02:00
7b8df80828 Use display manager for login.
Some checks are pending
CodeQL / Analyze (push) Waiting to run
Java CI with Gradle / build (push) Waiting to run
Deploy Jekyll site to Pages / build (push) Waiting to run
Deploy Jekyll site to Pages / deploy (push) Blocked by required conditions
2025-08-01 17:41:03 +02:00
fccf2a6b65 Fix branch evaluation.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2025-07-26 22:43:45 +02:00
00e9affee4 Fix evaluation of template source. 2025-07-26 15:18:22 +02:00
fa84110e1d Handle configuration value properly. 2025-07-14 17:34:09 +02:00
76b579c404 Add key, allowing vue to optimize.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2025-05-04 11:36:55 +02:00
a5433c869b Upgrade webconsole base library. 2025-05-03 22:29:42 +02:00
10f3028f06 Increase concurrency and avoid race condition. 2025-04-30 16:27:15 +02:00
b7fad4614d Improve debug messages.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2025-04-29 14:02:12 +02:00
7d298ce24b Clarify intend. 2025-04-14 21:39:35 +02:00
6ef4c2aaa2 Fix return value. 2025-04-14 12:08:48 +02:00
5bcf0ba051 Add umami to javadoc. 2025-04-13 17:01:38 +02:00
d67f374de7 Try umami. 2025-04-13 16:48:42 +02:00
2b3420c801 Update component picture.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2025-03-31 12:21:47 +02:00
bd54d293eb Update picture. 2025-03-30 21:58:22 +02:00
cb2ae7c33e Update. 2025-03-30 21:32:55 +02:00
85a9b41046 Update picture. 2025-03-30 21:12:18 +02:00
fb976802cf Minor edit. 2025-03-30 13:05:33 +02:00
af112bb66b Editorial changes. 2025-03-30 12:37:49 +02:00
592b30f6c5 Update state diagram. 2025-03-30 12:17:14 +02:00
c716e32534 Make tests work again. 2025-03-30 11:42:03 +02:00
c79d678a2a More consistent logging. 2025-03-29 18:38:09 +01:00
f30ea79abb Minor editorial changes. 2025-03-29 17:41:01 +01:00
d7d5c860a2 Merge branch 'wip/optimize' 2025-03-29 15:10:32 +01:00
991763f228 Optimize state change handling.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2025-03-29 15:09:38 +01:00
db49f5ba2f Restart non-deleted pods. 2025-03-28 21:28:55 +01:00
2e70ef2b98 Merge branch 'feature/pod-restart' 2025-03-28 18:03:54 +01:00
e8097d87d9 Let the operator manage pod restarts. 2025-03-28 18:03:09 +01:00
7a70d73330 Merge branch 'feature/pools'
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2025-03-24 15:05:31 +01:00
3143a1be93 Remove no longer valid optimization.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2025-03-24 15:04:39 +01:00
4bcbafb4f1 Improve label. 2025-03-22 11:25:02 +01:00
331b6d8d61 Minor edit. 2025-03-21 09:18:38 +01:00
725fb663c8 Merge branch 'feature/pools'
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2025-03-20 18:47:46 +01:00
d9799df861 Delete assignments when pool is deleted. 2025-03-20 18:46:45 +01:00
fe1d56517b Reorganize handlers. 2025-03-20 18:29:45 +01:00
359b1fdb84 Clarify pipeline usage. 2025-03-20 18:02:14 +01:00
16a15bc9ad Document memory allocation. 2025-03-20 09:33:10 +01:00
7644e65ab0 Merge branch 'wip/optimize' 2025-03-19 22:59:14 +01:00
dbc89e6e09 Avoid unnecessary processing. 2025-03-19 22:57:58 +01:00
9baf9b7673 Reset runner info when pod is deleted. 2025-03-18 21:48:10 +01:00
3686629a28 Fix race condition. 2025-03-18 16:44:15 +01:00
5991fe0d2d Merge branch 'wip/optimize' 2025-03-17 16:50:29 +01:00
3b0a4c8a23 Rate limit for RAM size updates. 2025-03-17 16:49:10 +01:00
5ca45d7620 Minor edit.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2025-03-16 23:12:22 +01:00
efd489b22f Allow VM operator to watch pods. 2025-03-16 23:03:19 +01:00
9644e5fd83 Improve debug message. 2025-03-16 23:02:59 +01:00
fe18bb3cdf Consolidate debug messages. 2025-03-16 23:02:44 +01:00
9a557f5019 Merge branch 'wip/optimize' 2025-03-16 17:08:40 +01:00
fd0f4f8eb2 Fetch display secret only when needed. 2025-03-16 17:01:36 +01:00
9bd17e8899 Update. 2025-03-16 15:44:27 +01:00
227c097c01 Actively add pod info, don't run queries. 2025-03-16 15:13:16 +01:00
ce4d0bfb72 More features, more resources. 2025-03-16 14:53:01 +01:00
017607f2e2 Prune not required data before transfer. 2025-03-15 15:21:44 +01:00
fcdb537f35 Merge branch 'fix/condition-update' 2025-03-15 12:35:23 +01:00
5309460fbf Prevent update of lastTransitionTime if we have no transition. 2025-03-15 12:33:20 +01:00
dc228295d1 Update. 2025-03-15 11:25:13 +01:00
1b1e5ffb8c Reduce default logging. 2025-03-14 21:16:01 +01:00
a3d6db3178 Merge branch 'feature/vm-info'
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2025-03-14 18:39:34 +01:00
197c21bc32 More consistent logging. 2025-03-14 18:37:43 +01:00
8bc413b2da Update templates when testing. 2025-03-14 18:37:05 +01:00
ba582d877e Display runner version in GUI. 2025-03-14 17:52:25 +01:00
3a4404b758 Add runner version to status. 2025-03-14 16:57:58 +01:00
f0ebea5353 Use two digit numbers for VMs. 2025-03-14 12:33:59 +01:00
084bdd1f46 Merge branch 'feature/auto-login' 2025-03-14 11:22:53 +01:00
5947bd3684 Avoid unnecessary config map changes. 2025-03-14 11:21:29 +01:00
c02b3d99cb Restore reasonable default. 2025-03-13 18:42:52 +01:00
407aa4b4d1 Merge branch 'fix/runner-poweroff' 2025-03-13 18:40:55 +01:00
3df01fcad0 Add debug messages. 2025-03-13 18:19:09 +01:00
2d16cbc352 Add powerdown through guest agent (more reliable). 2025-03-13 17:45:28 +01:00
2a3774ae24 Minor edits. 2025-03-13 15:46:11 +01:00
d637cb2c72 Move constants to separate class. 2025-03-13 10:34:36 +01:00
f493a2c582 Use process exit as termination confirmation. 2025-03-13 10:25:32 +01:00
72e1b8a580 Merge branch 'fix/runner-poweroff' 2025-03-12 21:50:56 +01:00
ecb43db83e Test configuration. 2025-03-12 21:48:18 +01:00
2a33f468f2 Track connection closing. 2025-03-12 21:46:46 +01:00
36877666f3 No QMP poweroff if QMP not available. 2025-03-12 20:59:36 +01:00
fae75dafa9 Merge branch 'fix/race-condition' 2025-03-12 17:45:07 +01:00
46f079504c Update. 2025-03-12 17:40:58 +01:00
5d0c6c6423 Fix startup. 2025-03-12 17:22:46 +01:00
19968ab73e Move code to agent. 2025-03-12 15:04:03 +01:00
68a688c4ce Intermediate state. 2025-03-12 11:56:32 +01:00
44868464b9 Update title. 2025-03-09 17:07:08 +01:00
61286a528c Add hint. 2025-03-09 16:43:19 +01:00
ce1a9afec7 Fix layout. 2025-03-09 15:52:35 +01:00
591278a07f Edit README.md 2025-03-08 05:37:40 +00:00
29bc6f539c Add hint regarding requirements. 2025-03-07 20:52:10 +01:00
2aa4116e95 Merge branch 'main' into feature/pools 2025-03-07 13:27:35 +01:00
7104984ac7 Consistent spelling for QEMU.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2025-03-07 13:26:31 +01:00
7f7306fc4a Merge branch 'feature/pools' 2025-03-06 23:32:05 +01:00
7130c128bb Add another environment setting. 2025-03-06 23:30:57 +01:00
3557e5fc27 Merge branch 'feature/pools' 2025-03-06 14:44:41 +01:00
f3907ffae9 Fix some markdown style issues. 2025-03-06 14:40:50 +01:00
9459c367ac PMD is wrong. 2025-03-06 13:03:04 +01:00
c5a00acf3d "Fix" PMD warning. 2025-03-06 13:02:53 +01:00
1a8412d767 Fix deepCopy for arrays.
Isn't used in project.
2025-03-06 12:59:06 +01:00
21d2fe2dbd Merge branch 'main' into feature/auto-login 2025-03-06 11:46:31 +01:00
607379b06d Merge branch 'feature/auto-login' 2025-03-06 11:45:36 +01:00
c51da8650a Editorial changes. 2025-03-06 11:43:32 +01:00
12c72b3f52 Update link. 2025-03-06 09:16:05 +01:00
7dc68b5ac7 Add documentation. 2025-03-05 23:24:03 +01:00
5ae162445c Add number format. 2025-03-05 18:10:54 +01:00
fb73a17748 Fix link. 2025-03-05 15:38:08 +01:00
961098a984 Merge branch 'main' into feature/auto-login 2025-03-05 15:37:54 +01:00
0e57a4e862 Merge branch 'feature/auto-login' 2025-03-05 13:37:09 +01:00
2524172c12 Centralize evaluation of console accessibility. 2025-03-05 13:02:00 +01:00
7437a17c9f Moved missed condition. 2025-03-05 10:24:40 +01:00
7e650bf980 Use more constants. 2025-03-05 10:18:16 +01:00
940913cf89 Use UserLoggedIn condition for access control. 2025-03-04 23:39:22 +01:00
04ccdd7dee Add condition for logged in state. 2025-03-04 21:35:36 +01:00
bfe4c2bb32 Set loggedInUser via Reconciler. 2025-03-04 13:33:54 +01:00
28b1903acc Extend accessibility check. 2025-03-04 09:00:13 +01:00
dfe3038463 So auto login according to pool setting. 2025-03-03 21:26:10 +01:00
701194799f Reduce to secret query. 2025-03-03 21:19:34 +01:00
17e2a7c6f0 Fix some codacy issues. 2025-03-03 12:39:50 +01:00
eb3979dc83 Consistent indentation. 2025-03-03 11:59:40 +01:00
e871fc059b Add bing site auth. 2025-03-03 11:11:55 +01:00
d4fdd4209f Activate sitemap. 2025-03-03 10:57:23 +01:00
51a72a162d Sitemap generated by jekyll is incomplete. 2025-03-03 10:50:58 +01:00
fa53a07a52 Update. 2025-03-03 10:41:16 +01:00
6b6a33e702 Need full fetch for javadoc build. 2025-03-03 10:21:33 +01:00
cc78c38efe Remove no longer needed sub-directory. 2025-03-03 10:16:05 +01:00
c004265f5e Don't merge into jdrupes.org any more. 2025-03-03 09:25:58 +01:00
5c7a9f6e5f Merge branch 'feature/auto-login' 2025-03-03 09:21:05 +01:00
03fdabe85a Edit README.md 2025-03-03 06:50:03 +00:00
214085119c Add style for searching. 2025-03-02 23:15:19 +01:00
30bc119178 Update. 2025-03-02 22:57:22 +01:00
987f634f40 Update. 2025-03-02 22:33:22 +01:00
083c6db2da Build own site. 2025-03-02 22:12:43 +01:00
7670857d0a Separate sites. 2025-03-02 21:50:24 +01:00
d6e2a92fe8 Fix url. 2025-03-02 21:20:25 +01:00
d8cff8b942 Track vm-operator separately. 2025-03-02 21:06:26 +01:00
4965845f3d Move robots.txt. 2025-03-02 20:44:39 +01:00
a0dfd25192 Add robots.txt. 2025-03-02 20:29:56 +01:00
fd0bcc9307 Move main site to vm-operator.jdrupes.org. 2025-03-02 20:10:42 +01:00
0e28bcd038 Change (main) site. 2025-03-02 18:48:13 +01:00
60349bca78 Shorten title to make bing happy. 2025-03-02 13:45:38 +01:00
199cd8185e Fix robots.txt "generation". 2025-03-02 13:15:38 +01:00
f6338758d8 Sitemap property must be an absolute url. 2025-03-02 13:13:03 +01:00
3a94602a0d Add sitemap. 2025-03-02 13:02:46 +01:00
07fb07a6a4 Javadoc is hosted on main site only. 2025-03-02 12:57:26 +01:00
687a050ec4 Generate sitemap. 2025-03-02 12:55:40 +01:00
2f6b3d2127 Fix footer. 2025-03-02 12:39:48 +01:00
05d53c58b1 Fix footer. 2025-03-02 12:28:08 +01:00
d7af1f5d06 Fix footer. 2025-03-02 12:23:02 +01:00
6b7c78ed2c Add readthedocs specific footer. 2025-03-02 12:13:33 +01:00
502842f486 Trigger generation of a canonical url. 2025-03-02 12:05:32 +01:00
5b7531c5e5 Add for readthedocs. 2025-03-02 11:51:47 +01:00
d0298eb7e8 Update. 2025-03-02 11:32:36 +01:00
01db49397a Try readthedocs. 2025-03-02 11:28:08 +01:00
f8cc26e657 Define some more constants. 2025-03-01 22:51:51 +01:00
41ae658e0c Reorganize imports. 2025-03-01 21:51:33 +01:00
e822d472f9 Optimize status update. 2025-03-01 17:44:52 +01:00
4a242c4657 Clear logged in user on startup and shutdown. 2025-03-01 17:12:02 +01:00
62a7210117 Fix and clarify usage of methods for status update. 2025-03-01 17:00:14 +01:00
5e282c4d2b Move automatic login request to CRD.
Undoes reorganize constants.
2025-03-01 15:44:05 +01:00
5366e24092 Move automatic login request to CRD.
Also reorganizes constants.
2025-03-01 11:02:52 +01:00
3152ff842b Allow console take over. 2025-02-27 22:55:50 +01:00
b409443499 Fix icon. 2025-02-27 22:54:19 +01:00
5ec220d0a6 Use gids for id management and isolate home directories. 2025-02-27 17:41:57 +01:00
5cbdab9da8 Fix log message. 2025-02-27 14:55:31 +01:00
b4cb3b8694 Control login. 2025-02-27 14:46:13 +01:00
59bf4937ef Avoid multi-line message. 2025-02-27 14:45:25 +01:00
3119349450 Fix user switching. 2025-02-26 22:33:08 +01:00
b4bc0c7b0f Delay enable until VM operator agent started. 2025-02-26 22:00:32 +01:00
a1e941276e Working login script. 2025-02-26 21:59:38 +01:00
4a7a309f07 Get started with vmop-agent. 2025-02-25 15:43:47 +01:00
d2c39dc06a Rename. 2025-02-25 13:48:23 +01:00
2119c215fc Prevent publishing doc in branches (except main). 2025-02-25 10:44:56 +01:00
d1bc335db9 Prepare auto login. 2025-02-24 21:21:58 +01:00
c6704c886f Avoid duplicate constants. 2025-02-24 18:18:29 +01:00
bc33640c98 Avoid duplicate constants. 2025-02-24 18:09:14 +01:00
ddab466fd0 Restrict pagefind search to project. 2025-02-24 14:03:49 +01:00
c45c452c83 Adjust class name. 2025-02-24 13:20:28 +01:00
e3b5f5a04d Refactor QEMU socket connection handling and start vmop agent. 2025-02-24 11:58:13 +01:00
f236b376ae Back to testing. 2025-02-23 12:05:55 +01:00
558f4d96c9 Add pagefind. 2025-02-23 12:00:27 +01:00
5b8b47f95c Add some metadata to make bing happy. 2025-02-23 11:47:13 +01:00
3012da3e87 Add login information to display secret. 2025-02-23 11:14:46 +01:00
0828d03835 Javadoc fixes. 2025-02-22 21:27:39 +01:00
81b128e4a3 Clarify responsibilities of display secret monitor and reconciler. 2025-02-22 21:24:58 +01:00
e291352828 Prepare usage of guest os command. 2025-02-21 20:54:27 +01:00
5ad052ffe4 Delay console opening for pool VMs. 2025-02-19 21:04:08 +01:00
c582763fbf Merge branch 'feature/booted-status' 2025-02-18 16:53:22 +01:00
777ae73c74 Add OS icons. 2025-02-18 16:50:43 +01:00
bccc4ac219 Add pretty os name to displayed data. 2025-02-18 12:15:50 +01:00
ec8152bd51 Add booted state. 2025-02-17 20:47:00 +01:00
e4bba582a0 Merge branch 'prep/v4.0.0' 2025-02-17 10:23:23 +01:00
0287ae7998 Merge branch 'testing' 2025-02-17 10:20:31 +01:00
46cb2466fe Merge branch 'feature/auto-login' into prep/v4.0.0 2025-02-17 09:52:37 +01:00
3e713b4ff2 Extend comment. 2025-02-17 09:49:06 +01:00
0ded0ff9a9 Add usage info. 2025-02-16 21:06:49 +01:00
5078001f4b Add guest agent client and retrieve OS info. 2025-02-10 22:24:10 +01:00
1fc26647b6 Add maintenance script. 2025-02-02 16:50:52 +01:00
aea8b9540e Merge branch 'prep/v4.0.0' into testing 2025-02-02 13:59:44 +01:00
d27339b1e9 Make VM extra data a class. 2025-02-02 13:54:10 +01:00
d5e589709f Handle conflict properly. 2025-02-02 12:10:22 +01:00
21108771d9 Fix warning. 2025-02-01 22:10:47 +01:00
b7ea6860ff Adapt name to previous change. 2025-02-01 22:08:57 +01:00
85a4521299 Combine VmDefinitionModel and VmDefinition. 2025-02-01 22:06:30 +01:00
b250398213 Merge branch 'testing' into prep/v4.0.0 2025-02-01 18:53:06 +01:00
54747b25e8 Use VmChannel's event pipeline to update assignment. 2025-02-01 18:51:19 +01:00
9986e4c8bf Merge branch 'prep/v4.0.0' into testing 2025-01-31 22:22:27 +01:00
b5ae22a8ea Avoid duplicate assignment. 2025-01-31 22:09:17 +01:00
b78b33a6f1 Merge branch 'prep/v4.0.0' 2025-01-31 15:58:56 +01:00
b159bae5da Allow users to start assigned VMs. 2025-01-31 15:26:42 +01:00
23bc41d68d Prefer running VMs for new assignments. 2025-01-31 15:26:25 +01:00
6a1273e701 Prevent concurrent modification exception. 2025-01-31 12:24:21 +01:00
4fc0d6fc63 Support both string and boolean for deleteConnectionFile. 2025-01-31 11:15:43 +01:00
ecd7ba7baf Fix trailing space. 2025-01-30 22:32:51 +01:00
150b9f2908 Fix spaces. 2025-01-30 22:14:09 +01:00
29dd6aab82 Javadoc fixes. 2025-01-30 22:04:41 +01:00
99c96e44c3 Allow access to vmpools. 2025-01-30 22:00:10 +01:00
1f4d69075a Merge branch 'fix/cloud-init-hostname' into 'main'
Generate metaData even if only cloudInit is specified.

See merge request org/jdrupes/vm-operator!13
2025-01-30 19:56:39 +00:00
ad79e8542a Merge branch 'fix/cloud-init-hostname' into testing 2025-01-30 16:57:26 +01:00
e447a944dc Generate metaData even if only cloudInit is specified. 2025-01-30 16:55:54 +01:00
49566584a2 Merge branch 'feature/console-action' into 'main'
Add more actions to VM management conlet.

See merge request org/jdrupes/vm-operator!12
2025-01-30 11:47:42 +00:00
e4e00c8ec8 Merge branch 'feature/console-action' into testing 2025-01-29 21:06:41 +01:00
ebda41346a Simplify permission management. 2025-01-29 21:01:49 +01:00
8d96307bb5 Add reset action to VM management. 2025-01-29 18:42:10 +01:00
af41c78c07 Add console access to VM management. 2025-01-29 17:33:16 +01:00
5cd4edcec1 Don't add channels until fully initialized. 2025-01-28 18:09:31 +01:00
85be5b9cbf Merge branch 'feature/pools' into 'main'
Add VM pools

See merge request org/jdrupes/vm-operator!11
2025-01-27 11:50:53 +00:00
50ad911265 Merge branch 'feature/pools' into testing 2025-01-27 11:11:25 +01:00
86f6ece264 Adjust auto close time. 2025-01-26 21:55:24 +01:00
1b5ad5b73e Prevent unauthorized console take over. 2025-01-26 21:49:37 +01:00
3ca632c8da Exchange columns. 2025-01-26 17:21:36 +01:00
e7cc7cc879 Enhance console connection entry. 2025-01-26 15:58:38 +01:00
981cbe2744 Improve readability. 2025-01-26 14:52:26 +01:00
224855efd3 Disable empty lists. 2025-01-25 22:27:52 +01:00
aaf1a0c545 Add operator for testing. 2025-01-25 22:27:07 +01:00
53a58a2aca Add users for pool testing. 2025-01-25 15:14:58 +01:00
574ad5226b Fix typescript error. 2025-01-25 14:33:56 +01:00
a0d626cc31 Fix warnings. 2025-01-25 13:40:51 +01:00
2a70c74234 Use consistent method names. 2025-01-25 13:39:22 +01:00
5d722abd2e Add assignment based on last usage. 2025-01-25 13:35:51 +01:00
877d4c69cd Remove obsolete method. 2025-01-25 13:34:16 +01:00
80d4165500 Upgrade base library. 2025-01-25 13:32:27 +01:00
a5ddf6ac97 Avoid updating immutable fields. 2025-01-25 13:32:00 +01:00
9318b1279a Move jackson to base library. 2025-01-23 21:17:06 +01:00
fb69c1d793 Minor style change. 2025-01-23 18:25:37 +01:00
edc3596e7d Move "used from" to details. 2025-01-23 18:05:38 +01:00
ba18e1f0d0 Add assigned to. 2025-01-23 17:35:46 +01:00
8799bcc8f2 Update permissions on VM change. 2025-01-23 15:33:01 +01:00
1cb90b0c94 Update GUI on pool permission changes. 2025-01-23 15:25:16 +01:00
6d5ba8829c Basically working. 2025-01-23 13:41:53 +01:00
d060a9334a Return only defined pools. 2025-01-18 17:50:33 +01:00
9b47ad3136 Don't duplicate pool management. 2025-01-18 17:16:54 +01:00
76be59a5b3 Don't duplicate VM management. 2025-01-18 17:02:30 +01:00
5bd6700541 Name not needed. 2025-01-15 22:06:00 +01:00
bd5227fda3 Simplify pool management. 2025-01-15 21:58:08 +01:00
4943baf3e3 Save assignment information. 2025-01-14 10:23:32 +01:00
15ac0721a6 Minor refactoring. 2025-01-14 10:22:56 +01:00
db7fbe2b7c Cleanup CR deletion. 2024-12-02 12:18:41 +01:00
84ac4bb28c Add hashCode and equals. 2024-12-01 16:43:35 +01:00
c3428ea4a5 Rename pool manager to monitor. 2024-12-01 14:22:18 +01:00
2dc93f1370 Unify permission usage. 2024-12-01 14:21:25 +01:00
367aebeee5 Pool configurable in GUI. 2024-12-01 13:41:18 +01:00
77cfcff2ed Fix toString. 2024-11-29 14:17:13 +01:00
4c600e7118 Merge branch 'main' into testing 2024-11-24 16:59:10 +01:00
e839f7b3b2 Rename conlet. 2024-11-23 14:08:45 +01:00
4ceaaa9fa2 Provide backward compatibility for configuration. 2024-11-23 14:08:01 +01:00
2be88d0f34 Remove viewer. 2024-11-23 13:06:51 +01:00
c361f9296d Remove conlet. 2024-11-23 13:03:04 +01:00
dc21dc8a7b Test pools. 2024-11-23 12:56:32 +01:00
64035d8dc1 Remove poolaccess conlet. 2024-11-23 12:55:48 +01:00
22446c3618 Clarify documentation. 2024-11-23 12:54:57 +01:00
5efef2a083 Deliver events on dedicated pipeline. 2024-11-23 12:54:29 +01:00
eabb2d9cf0 Add method. 2024-11-23 12:52:42 +01:00
27f983c18d Rename vmviewer to vmaccess. 2024-11-23 12:50:25 +01:00
00adeba625 Add pool manager. 2024-11-19 17:10:55 +01:00
0f4768d707 Merge timestamp fix. 2024-11-17 16:33:34 +01:00
5a68b976c8 Merge branch 'main' into testing 2024-11-17 16:33:13 +01:00
4690b897e9 Adapt to changed timestamp format.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-11-17 16:32:55 +01:00
043666a932 Generate test VMs. 2024-11-17 15:08:22 +01:00
bc0d25d00e Merge fix from main. 2024-11-17 13:21:16 +01:00
44ae5d405b Merge branch 'main' into testing 2024-11-17 13:20:37 +01:00
e7da41f838 Fix summary evaluation. 2024-11-17 13:20:20 +01:00
5d90a6a8a9 Move test to tc1. 2024-11-17 12:21:16 +01:00
1495301ec8 Merge branch 'main' into feature/pools 2024-11-16 17:34:35 +01:00
f513e6c395 Merge branch 'main' into testing 2024-11-16 17:34:06 +01:00
dec4c11785 Fix sync selection. 2024-11-16 17:33:37 +01:00
13cd262a47 New CRD. 2024-11-16 17:15:31 +01:00
a7ee3d0515 Merge branch 'main' into testing 2024-11-15 17:31:06 +01:00
31758b5ef1 Add fallbacks for efi vars as well. 2024-11-15 17:30:50 +01:00
97e94a8e9a Merge branch 'main' into testing 2024-11-15 15:29:37 +01:00
28df6ede15 Force fixed webconsole library. 2024-11-15 15:29:19 +01:00
d8132de6c2 Merge branch 'main' into testing 2024-11-15 09:59:18 +01:00
558d8f9548 Fix layout. 2024-11-15 09:58:30 +01:00
dc7382dc86 Always update console user. 2024-11-15 09:53:34 +01:00
6e3f554d8d Merge branch 'main' into testing 2024-11-14 22:10:57 +01:00
e864f677c3 Allow operator to patch CR status. 2024-11-14 22:10:18 +01:00
93a1a2b2f9 Merge branch 'main' into testing 2024-11-14 21:24:06 +01:00
69507b540c Improve messages. 2024-11-14 20:17:33 +01:00
0ba8d922ef Add used by information. 2024-11-14 18:46:19 +01:00
4ea568ea17 Automatically repeat status update in case of conflict. 2024-11-14 12:40:45 +01:00
811164f7b9 Move api client to base class. 2024-11-14 11:59:14 +01:00
f1d973502d Fix transparency. 2024-11-14 11:58:51 +01:00
4d447717c2 Improve tracking. 2024-11-13 23:45:54 +01:00
9773207307 Better name. 2024-11-13 11:14:53 +01:00
82a6d53156 Merge remote-tracking branch 'origin/main' into feature/track-clients 2024-11-13 11:03:21 +01:00
5ec7f58bbd Merge remote-tracking branch 'origin/main' into testing 2024-11-12 22:56:06 +01:00
228322748b Use 4m bios if smaller version is not available. 2024-11-12 22:41:54 +01:00
40cbeb694b Avoid NPE. 2024-11-12 22:04:15 +01:00
5897b4b386 Merge remote-tracking branch 'origin/main' into testing 2024-11-12 20:49:51 +01:00
65ceed93b6 Merge branch 'fix/choose-bios' into 'main'
Enhance logging

See merge request org/jdrupes/vm-operator!10
2024-11-12 19:29:46 +00:00
12d6745d75 Add some logging messages and enhance logging configurability. 2024-11-12 19:29:46 +00:00
0aaa375ffc Merge remote-tracking branch 'origin/main' into testing 2024-11-10 17:21:16 +01:00
b8aa925a49 Merge branch 'feature/track-clients' into 'main'
Feature/track clients

See merge request org/jdrupes/vm-operator!9
2024-11-10 16:19:46 +00:00
355eded86b Add tracking of client (viewer) connection state and visualize in GUI. 2024-11-10 16:19:46 +00:00
090d504b77 Add in-use visualization. 2024-11-10 17:10:08 +01:00
e5fd45ebcb Show console client. 2024-11-10 14:41:45 +01:00
12408143a7 Fix warning. 2024-11-10 14:15:17 +01:00
c7b65ca581 Report console connection events. 2024-11-10 14:13:34 +01:00
4d76225442 Fix image path. 2024-11-09 13:09:11 +01:00
9019907224 Add "testing" to branches with default versioning. 2024-11-09 13:03:45 +01:00
5b209c935e Pull from "testing" when using this branch. 2024-11-09 12:45:39 +01:00
3d446836b5 Add "testing" to branches with default versioning. 2024-11-09 12:41:22 +01:00
2d51421e19 Merge branch 'fix/viewer-control' into 'main'
Fix/viewer control

See merge request org/jdrupes/vm-operator!7
2024-11-09 11:08:59 +00:00
abe06b4658 Adapt viewer preview controls to permission changes. 2024-11-09 11:08:59 +00:00
a9c31a378e Merge branch 'feature/less-gson' into 'main'
Feature/less gson

See merge request org/jdrupes/vm-operator!6
2024-11-08 16:48:08 +00:00
c8781c2d8e Use less gson internally. 2024-11-08 16:48:07 +00:00
8e692a03fe Upgrade webconsole.base.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-10-26 21:40:23 +02:00
3708244571 Fix warnings. 2024-10-26 21:16:50 +02:00
23703bad3c Adapt to new webconsole base. 2024-10-26 21:11:50 +02:00
80fe921e6e Cleanup. 2024-10-26 20:47:00 +02:00
1bc63abadf Merge branch 'cleanup/v3.4' into 'main'
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Prepare release v3.4

See merge request org/jdrupes/vm-operator!5
2024-10-06 10:05:10 +00:00
54445ef531 Prepare release v3.4 2024-10-06 10:05:09 +00:00
31a3f79e2a Merge branch 'feature/no-sts' into 'main'
Deploy pod instead of stateful set

See merge request org/jdrupes/vm-operator!4
2024-10-04 15:01:58 +00:00
83908b7cfd Deploy pod instead of stateful set 2024-10-04 15:01:58 +00:00
525696ffe9 Merge branch 'feature/delConnFile' into 'main'
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Feature/del conn file

See merge request org/jdrupes/vm-operator!3
2024-09-13 10:17:20 +00:00
5c736faf09 Add "delete-this-file=1" in connection file by default. 2024-09-13 10:17:20 +00:00
69a9629ea9 Minor edits. 2024-08-20 10:09:59 +02:00
9856284e39 Merge branch 'bug/ioexception' into 'main'
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Use Java-21 virtual threads

See merge request org/jdrupes/vm-operator!2
2024-08-10 10:41:50 +00:00
bef47b308d Use Java-21 virtual threads 2024-08-10 10:41:50 +00:00
82eb6671a3 Merge branch 'feature/multi-monitor' into 'main'
Feature/multi monitor

See merge request org/jdrupes/vm-operator!1
2024-08-05 20:21:36 +00:00
4384c18910 Feature/multi monitor 2024-08-05 20:21:36 +00:00
51c1d8d7a9 Minor edit. 2024-07-17 22:59:28 +02:00
605ede7cf3 Merge branch 'main' of github.com:mnlipp/VM-Operator 2024-07-17 22:46:14 +02:00
4c04bb0e0a Minor edit. 2024-07-17 22:26:29 +02:00
Michael N. Lipp
45e271e6d0
Update webgui.md 2024-07-17 07:55:16 +02:00
Michael N. Lipp
959a35ca9e
Update manager.md 2024-07-17 07:52:03 +02:00
Michael N. Lipp
a62b8c2899
Update index.md 2024-07-15 08:46:23 +02:00
1b2d7ec330 Fix link. 2024-07-11 13:21:46 +02:00
e7cdaea205 Minor edit. 2024-07-11 12:54:12 +02:00
33856fffc2 Fix link. 2024-07-11 12:52:28 +02:00
e3fc4747e4 Minor edits. 2024-07-09 17:31:58 +02:00
fcf0c1d1af Fix syntax. 2024-07-09 17:19:15 +02:00
8f8a38771e Use longer titles. 2024-07-09 17:09:52 +02:00
f45c5698d1 Update. 2024-07-09 11:21:14 +02:00
299bded9de Add backlink. 2024-07-06 10:35:26 +02:00
9a5e1800ff Remove duplicate h1. 2024-07-04 09:38:02 +02:00
8802666944 Fix link. 2024-07-02 13:39:20 +02:00
63e77c0a8a Fix HTML error. 2024-06-30 15:09:20 +02:00
e3da87d94f Version tag may only be applied when all projects are "clean". 2024-06-27 16:50:49 +02:00
b74de67c6d Update icon. 2024-06-27 14:56:06 +02:00
6852c575ae Fix hover. 2024-06-26 21:43:28 +02:00
1fe7960d24 Update. 2024-06-26 14:14:45 +02:00
fc29786afe Fix links.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-06-24 11:18:35 +02:00
8a434d8410 Use newest branch if we have multiple matches. 2024-06-24 10:04:09 +02:00
e994fa1543 Another attempt to find the branch. 2024-06-23 21:41:00 +02:00
8b83a0cbc8 Make sure to switch to branch. 2024-06-23 17:02:44 +02:00
7df0cc386c Fix image building. 2024-06-23 12:52:18 +02:00
12ca211fdb Fix env reference. 2024-06-23 11:25:19 +02:00
e830815400 Add dependency. 2024-06-22 23:03:28 +02:00
28691b6443 Make dependencies branch specific. 2024-06-22 16:06:31 +02:00
86837bc81b Fix token.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-06-22 15:11:01 +02:00
a50809211d Fix name.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-06-22 14:46:54 +02:00
63c03dae81 Publish javadoc with JDK-21. 2024-06-22 14:40:14 +02:00
7dea95660c Update. 2024-06-22 14:32:11 +02:00
128ffbc2ad Update. 2024-06-22 11:52:32 +02:00
9d5f3cf702 Update link. 2024-06-20 23:11:01 +02:00
6a2d711643 New publishing style. 2024-06-20 22:42:26 +02:00
f0ccb83b39 Move pages. 2024-06-20 20:41:33 +02:00
98ce74f42b Adjust link. 2024-06-18 21:32:01 +02:00
92f4b3aaf8 Avoid unnecessary pages runs. 2024-06-18 18:03:14 +02:00
Michael N. Lipp
7f80f4c6e9
Wip/gitlab ci (#32)
Start CI/CD on a gitlab instance.
2024-06-18 13:51:59 +02:00
Michael N. Lipp
10182efea1
Feature/use java21 (#31)
Switch to using Java-21.
2024-06-13 22:15:33 +02:00
9c31f574b8 Fix accessing console without password.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-06-12 20:53:18 +02:00
d5c9a0c302 Upgrade base library.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-06-10 23:10:11 +02:00
6213aa5970 Use better version descriptors. 2024-06-10 23:09:48 +02:00
bbd9d3baff Upgrade OIDC login library.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-06-10 15:15:00 +02:00
Michael N. Lipp
65a5cfd286
Develop/v3 (#27)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Prepare release.
2024-06-09 22:54:42 +02:00
Michael N. Lipp
659463b3b4
Viewer ACL (#26)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Provide ACLs (together with general improvements) for the viewer conlet.
2024-06-01 11:12:15 +02:00
Michael N. Lipp
a6525a2289
Add viewer conlet (#25)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-05-27 12:57:01 +02:00
b6f0299932 Upgrade base library.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-05-21 09:03:20 +02:00
dac16da30b Adapt to usage of oidc login conlet. 2024-05-21 09:03:07 +02:00
6ffe287def Upgrade base library. 2024-05-18 12:10:37 +02:00
81ce3e244f Use OIDC login conlet. 2024-05-14 11:27:09 +02:00
8b6acceda8 Re-enable PMD. 2024-05-13 22:33:05 +02:00
Michael N. Lipp
7bd3b172a4
Wip/viewer link (#24)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-04-17 11:37:10 +02:00
b58c813e89 Make class thread safe. 2024-04-17 11:20:11 +02:00
98f5c1e402 Experimental spice proxy provider. 2024-04-10 11:14:32 +02:00
383d1c5cca Reflect display password changes in status. 2024-03-28 16:46:58 +01:00
Michael N. Lipp
9209ba0078
Fix search for secret. (#23)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-03-20 17:59:28 +01:00
Michael N. Lipp
690215d73c
Add permission (#22) 2024-03-20 17:40:41 +01:00
Michael N. Lipp
3103452170
Support for display secrets (#21)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-03-20 11:03:09 +01:00
Michael N. Lipp
85b0a160f3
Generate doc on GitLab (#20) 2024-03-18 13:15:59 +01:00
Michael N. Lipp
a2641da7f5
Refactor internal Kubernetes API and upgrade to official v19 (#19)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-03-14 20:12:37 +01:00
ee2de96c56 Fix javadoc.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-02-26 15:19:29 +01:00
e25358085f Move guestShutdownStops up one level. 2024-02-26 15:15:55 +01:00
b207e0226f Fix template error.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-02-25 17:22:06 +01:00
fb4a0206f1 Make result of guest shutdown configurable. 2024-02-25 15:49:56 +01:00
bbe2d6efbc Make this explicit (implied by replicas = 1). 2024-02-25 14:27:54 +01:00
aa7fdbee08 Allow guest to (finally) shutdown the VM. 2024-02-25 13:48:44 +01:00
1a608df411 Use locally administered address in example. 2024-02-22 10:13:14 +01:00
48f86a6ef2 Use commonly used prefix. 2024-02-22 09:59:51 +01:00
639e315769 Add network-config to CRD.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-02-21 22:21:32 +01:00
413f8f76dc Support cloud-init's network-config. 2024-02-21 16:14:37 +01:00
eccc35d0bf Make sure to get latest base image versions.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-02-20 21:51:48 +01:00
ee96f869da Add generation of fallback properties.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-02-18 13:35:31 +01:00
7835686304 Javadoc fix. 2024-02-18 13:17:51 +01:00
b255f0e946 Support cloudInit in CRD. 2024-02-18 11:46:09 +01:00
499c1822fd Do push when testing. 2024-02-17 19:42:44 +01:00
542c4eb61f Update diagram. 2024-02-17 17:50:03 +01:00
599f64da4c Provide fallback for instance-id. 2024-02-17 16:47:10 +01:00
24f762d28c Add cloud-init support in runner. 2024-02-17 14:37:12 +01:00
b5622a459c Fix searching for executable. 2024-02-17 14:26:26 +01:00
49370b507b More memory needed when handling a large number of VMs. 2024-02-04 22:03:02 +01:00
57edc36980 Use names consistently. 2024-02-01 23:16:25 +01:00
2d9d8de357 Add informative log message.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-01-12 23:13:16 +01:00
d85c957b33 Exit with error on connection failure with kubernetes API. 2024-01-12 20:34:37 +01:00
5ec67ba61a Allow event generation.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-01-09 18:46:08 +01:00
218825fb8c Update build.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-01-09 18:11:32 +01:00
f6b70684ca Avoid redundant Stop event.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2024-01-09 14:04:58 +01:00
245fad39a9 Add event generation. 2024-01-09 13:56:19 +01:00
41a0ef1adb Fail if API server cannot be contacted. 2024-01-09 10:19:04 +01:00
545106510e Update. 2024-01-08 22:58:43 +01:00
9b89bf8a96 Remove mavenLocal (should never have been committed). 2024-01-08 15:33:43 +01:00
03dfa7a4d8 Provide more context information.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2023-11-05 15:08:51 +01:00
203ea00c7b Fix checkstyle configuration. 2023-11-05 12:37:41 +01:00
7dd9921dbf Provide default logging configuration as resource. 2023-11-04 14:53:48 +01:00
886c5b436e Restrict viewer log level to config.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2023-11-04 14:41:12 +01:00
6e2d23d979 Use default log settings by default. 2023-11-04 14:37:26 +01:00
01e392fc9d Ensure that json does not change after association with channel.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2023-11-04 12:40:00 +01:00
bf1e05d597 Rename method. 2023-11-04 12:39:18 +01:00
4122fa25d2 Handle maximum CPUs properly. 2023-11-03 22:13:48 +01:00
91a9c8605c Upgrade webconsole base. 2023-11-03 22:13:02 +01:00
fdacb1e3d2 Upgrade sysinfo conlet.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2023-11-01 20:23:03 +01:00
Michael N. Lipp
350446b522
Bugfix/ts style2 (#18)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2023-11-01 17:12:29 +01:00
Michael N. Lipp
39a2a0389e
Bugfix/ts style (#17)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Fix style warnings.
2023-10-31 23:03:48 +01:00
Michael N. Lipp
6f45e7982a
Feature/web gui2 (#16)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Add oveview and enhance.
2023-10-30 23:10:26 +01:00
Michael N. Lipp
8567a2f052
Feature/web gui (#15)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Add node display.
2023-10-24 13:23:13 +02:00
Michael N. Lipp
b8e1074150
Feature/web gui (#14)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2023-10-22 14:47:39 +02:00
Michael N. Lipp
c8b85b882d
Feature/web gui (#13)
Improve build and distribution.
2023-10-22 14:19:33 +02:00
Michael N. Lipp
ae3941707a
Feature/web gui (#12)
Basic GUI functions (start/stop).
2023-10-21 22:16:10 +02:00
Michael N. Lipp
6491742eb0
Bugfix/cpu topology (#11)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2023-10-10 12:57:13 +02:00
Michael N. Lipp
7be1c407fe
Bugfix/cpu topology (#10)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2023-10-09 22:13:33 +02:00
Michael N. Lipp
d42900afe7
Feature/improve build (#9) 2023-09-22 16:50:38 +02:00
Michael N. Lipp
fe7f336376
Fix ref. (#8)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2023-09-16 22:12:53 +02:00
01c114bc18 Fix ref. 2023-09-16 22:06:37 +02:00
Michael N. Lipp
40479c7547
Release from master. (#7) 2023-09-16 21:36:01 +02:00
b7ed2dcc95 Release from master. 2023-09-16 21:30:46 +02:00
Michael N. Lipp
666c8a34cc
Delay QEMU configuration until QMP is configured. (#6)
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Fixes #4.
2023-09-16 13:39:30 +02:00
1aecb82843 Delay QEMU configuration until QMP is configured.
Fixes #4.
2023-09-16 13:20:42 +02:00
Michael N. Lipp
d8882359a6
Feature/status (#5)
Update CR's status (condition Running, current cpu and ram allocation).
2023-09-16 12:15:23 +02:00
3825a4d3fd Remove explicit toolchain setting. 2023-09-16 12:10:21 +02:00
3b0fb02fbe Improve error reporting. 2023-09-16 11:53:51 +02:00
f24b6aca52 Define RBAC for running in cluster. 2023-09-16 11:48:20 +02:00
ea6751282c Take different approach to finding CR.
Requires less privileges.
2023-09-16 11:47:46 +02:00
2210dbcae2 Log handling errors. 2023-09-16 11:43:51 +02:00
8a7d9d6621 Report current CPUs in status. 2023-09-15 11:51:19 +02:00
7f512082f0 Update. 2023-09-14 22:08:15 +02:00
3eb8ca514b Support eclipse configuration. 2023-09-14 22:07:01 +02:00
77a4a27461 Update. 2023-09-14 21:27:12 +02:00
9c60153c8b Set explicit encoding. 2023-09-14 20:57:48 +02:00
ce786ea70d Clear RAM usage on stop. 2023-09-14 19:03:45 +02:00
86ea626dd5 Update. 2023-09-14 18:59:46 +02:00
9d29266907 Report current RAM usage in status. 2023-09-14 18:59:18 +02:00
a045cba998 Fix (make use of) transient dependencies. 2023-09-14 14:55:50 +02:00
9ba0dd8dc3 Combine constants for easy use in templates. 2023-09-14 14:45:06 +02:00
74e05ce023 Provide "Running" condition in status. 2023-09-14 14:14:07 +02:00
f50a4af46c Namespace can be set for development. 2023-09-14 09:57:20 +02:00
2b471f3852 More resources used by both runner and manager. 2023-09-14 09:56:28 +02:00
Michael N. Lipp
d9c2f6edd3
Use branch names in version. (#3) 2023-09-12 12:04:49 +02:00
ffeaf31534 Use branch names in version. 2023-09-12 11:48:45 +02:00
72beccc8cd Remove setup-machine-id.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
Causes problems and is not needed.
2023-09-11 17:50:21 +02:00
431cb483da Add indent for gradle. 2023-09-02 20:58:46 +02:00
d595156587 Add badges.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2023-09-02 14:48:57 +02:00
da929d8942 Add project group to all projects.
Some checks failed
Java CI with Gradle / build (push) Has been cancelled
2023-09-02 14:40:32 +02:00
eedce49ded Prepare for PRs. 2023-09-02 12:47:04 +02:00
c89fb1a63c Merge branch 'main' of git@github.com:mnlipp/VM-Operator.git into main 2023-09-02 12:40:24 +02:00
5c0d7df741 Add editorconfig. 2023-09-02 12:40:07 +02:00
Michael N. Lipp
a315a86b46
Update Readme.md 2023-09-02 08:16:14 +02:00
Michael N. Lipp
d5284a04a1
Update Readme.md 2023-09-02 08:09:53 +02:00
50f9b3ec1a Rename task. 2023-09-01 22:31:53 +02:00
0419136a59 Use embedded YAML for load balancer configuration. 2023-09-01 22:25:38 +02:00
cdc37725ce Update. 2023-09-01 22:23:51 +02:00
717d732892 Not needed. 2023-09-01 22:23:23 +02:00
6840db635e Upgrade libraries. 2023-09-01 22:22:57 +02:00
e75f12c69f Prioritize messages. 2023-08-24 14:00:33 +02:00
998e9c6aa1 Force parallel GC. 2023-08-24 13:57:23 +02:00
e10256f04e There's no advantage in using graal. 2023-08-24 13:56:57 +02:00
5fe71670e2 Add useful startup information. 2023-08-24 13:56:23 +02:00
c13344d0e2 Force parallel GC. 2023-08-24 13:25:25 +02:00
70ecc39466 Don't round, CPU fractions are supported. 2023-08-24 13:24:32 +02:00
7e094e720b Restore arch image as default because of
https://github.com/mnlipp/VM-Operator/issues/1
2023-08-23 13:30:28 +02:00
c5818b6bf4 Improve error handling. 2023-08-23 12:00:52 +02:00
219d8aa91a Use alpine for testing and also create (sw)tpm. 2023-08-23 10:58:28 +02:00
ce1870d16d Adjust requested memory. 2023-08-23 10:56:41 +02:00
2d35738f5a Create and use alpine based image.
Arch based is simply too big.
2023-08-23 10:29:58 +02:00
9305ebe927 Add the paths used by alpine. 2023-08-22 23:05:23 +02:00
9af84091b5 fix erroneous usage of qemu-base. 2023-08-22 18:38:55 +02:00
66f8d15dc0 Remove deprecated (and redundant) property. 2023-08-22 14:44:13 +02:00
ef708476ca Be a bit more specific with exception handling. 2023-08-21 17:40:51 +02:00
79da75e806 Test before push. 2023-08-21 16:55:25 +02:00
b919eb17ac Additional fail safe. 2023-08-21 16:55:14 +02:00
0d5e680318 Filter duplicate events. 2023-08-21 10:06:57 +02:00
a3bbeafdf6 Provide default for runnerTemplate to make default for update effective. 2023-08-20 20:49:13 +02:00
9ed16f4c3a Add some debug logging. 2023-08-20 19:18:36 +02:00
c69ae44395 Disable direct access to flash. 2023-08-20 19:11:38 +02:00
aab15d8b6f Test size setting. 2023-08-19 18:29:47 +02:00
6b6e48650d Avoid trailing space. 2023-08-19 18:05:18 +02:00
9706a9af8c Fix order. 2023-08-19 18:00:38 +02:00
15d7589144 Pretty print size. 2023-08-19 17:56:10 +02:00
cc15803ad6 Add real test. 2023-08-19 15:46:03 +02:00
9b46cfa8bb Get CI running again. 2023-08-19 15:29:59 +02:00
6c98438fc5 Basics for automated test. 2023-08-19 15:22:31 +02:00
c006bd0a39 Extend test. 2023-08-19 10:51:19 +02:00
8461adbe7c Use Kubernetes format everywhere for consistency. 2023-08-19 10:46:12 +02:00
7141d381be Drop support for Kubernetes abbreviation. 2023-08-19 10:44:54 +02:00
3297ecd034 Add missing token. 2023-08-18 15:56:20 +02:00
1a5c4ee639 Does doc generation as well. 2023-08-18 15:41:29 +02:00
0ba4996408 Fix typo. 2023-08-18 15:34:09 +02:00
0e52acfdef Can, maybe, be simplified. 2023-08-18 15:33:54 +02:00
3968c36e35 Update description. 2023-08-18 14:50:45 +02:00
3296ed84d4 Adapt regexp. 2023-08-18 14:24:19 +02:00
365bdf249d Improve documentation. 2023-08-18 14:09:02 +02:00
3c9a9cc2b3 Merge branch 'main' of git@github.com:mnlipp/VM-Operator.git into main 2023-08-18 11:23:48 +02:00
823bcedf1e Change configuration format. 2023-08-18 11:23:17 +02:00
Michael N. Lipp
5281db5ce4
Also update javadoc. 2023-08-18 07:29:11 +02:00
417fc736d7 Default to Stopped. 2023-08-17 21:18:29 +02:00
4ed5168591 Generate load balancer service instead of unspecific service. 2023-08-17 20:24:10 +02:00
f4e8318b6e Add load balancer service info for testing. 2023-08-17 20:23:07 +02:00
5b12606b58 Add port info. 2023-08-17 18:00:58 +02:00
6ec91e7a7e Support additional metadata for services continued. 2023-08-17 15:43:04 +02:00
ff6b4eea8c No longer required. 2023-08-17 13:40:51 +02:00
7b2b44fa82 Use true as default. 2023-08-17 13:39:54 +02:00
0e3bb88497 Support additional metadata for services. 2023-08-17 13:38:19 +02:00
477db06f8d Deletions happen automatically due to owner references. 2023-08-17 11:09:51 +02:00
cf1432b973 Provide manager version to templates. 2023-08-16 18:07:30 +02:00
697cb04f3f Merge branch 'main' of git@github.com:mnlipp/VM-Operator.git into main 2023-08-16 17:36:09 +02:00
2ae9b29c77 Add ownership relation.
See https://github.com/argoproj/argo-cd/discussions/15072
2023-08-16 17:35:48 +02:00
4df29cb5c3 Add ownership relation. 2023-08-16 17:33:16 +02:00
d407c9cc6e Avoid NPE when handling delayed event. 2023-08-16 17:32:49 +02:00
c354e11592 Fix/add resource requirements. 2023-08-15 17:03:43 +02:00
e84b9e3e82 Fix/add resource requirements. 2023-08-15 17:02:59 +02:00
f56fe228aa The client library is so not thread safe, adapt usage.
See https://github.com/kubernetes-client/java/issues/100
2023-08-15 15:54:40 +02:00
6cf5ecadc2 Support template configuration. 2023-08-15 09:51:59 +02:00
Michael N. Lipp
81128f9289
Clarify version information 2023-08-15 08:11:14 +02:00
ffdc603122 Add intel-iommu device. 2023-08-14 22:22:26 +02:00
62be40205b Support node selection. 2023-08-14 18:47:13 +02:00
77be2f63e8 Fix mac address. 2023-08-14 18:39:00 +02:00
2404663045 Move memory parsing to utility class. 2023-08-14 17:17:34 +02:00
e7f572a954 Simplify evaluation. 2023-08-14 15:45:16 +02:00
04535a0e72 Fix name. 2023-08-14 11:01:21 +02:00
Michael N. Lipp
14fc78c07e
Update Readme.md 2023-08-14 06:25:59 +02:00
Michael N. Lipp
8eee782fcf
Update kustomization.yaml 2023-08-14 06:18:22 +02:00
Michael N. Lipp
e4c7b13773
Update kustomization.yaml 2023-08-14 06:16:49 +02:00
facc474a7a Use different port for dev example. 2023-08-13 22:27:50 +02:00
af49c952fd Fix Running/Stopped behavior. 2023-08-13 22:24:40 +02:00
97225b1c01 Remove obsolete line. 2023-08-13 22:16:33 +02:00
ef4576104f Update examples. 2023-08-13 17:42:24 +02:00
ec4e6c64ba Fix permissions. 2023-08-13 16:59:03 +02:00
367f36d806 Allow operations on services. 2023-08-13 15:52:19 +02:00
d2eea50093 Make storageClass for runner data configurable. 2023-08-13 15:17:58 +02:00
9745e854f7 Fix image handling. 2023-08-13 14:19:27 +02:00
db3b533c5d Add service reconciler. 2023-08-13 12:24:08 +02:00
19f435a75c Minor edits. 2023-08-13 11:41:10 +02:00
deb06723ee Generate documentation for package scoped classes. 2023-08-13 11:33:51 +02:00
Michael N. Lipp
d1a802c20a
Update Controller.java 2023-08-13 08:57:40 +02:00
Michael N. Lipp
7088f88788
Update Controller.java 2023-08-13 08:52:45 +02:00
Michael N. Lipp
cf37d5e4c7
Update kustomization.yaml 2023-08-13 08:09:28 +02:00
Michael N. Lipp
3267b79451
Update test-vm.yaml 2023-08-13 08:03:42 +02:00
Michael N. Lipp
62be8dcad1
Update test-vm.yaml 2023-08-13 08:02:49 +02:00
e1ea5f473d Support alternate image specification. 2023-08-12 23:26:33 +02:00
8acd98d703 Add example configurations. 2023-08-12 18:27:22 +02:00
9e83712c9b Move development config. 2023-08-12 18:27:02 +02:00
a796e304a2 Add missing permission. 2023-08-12 16:57:11 +02:00
8b93d7fb34 Fix device reference. 2023-08-12 15:54:18 +02:00
b0396ed8c1 Fix problem with creating new VM. 2023-08-12 15:28:26 +02:00
3e2a5af6fd Improve logging. 2023-08-12 15:05:56 +02:00
6310c7fb82 Update. 2023-08-11 14:00:16 +02:00
bf2c72891c Force configuration update. 2023-08-11 13:59:29 +02:00
6cdb27ff4e Make codacy really happy. 2023-08-11 13:26:36 +02:00
a4ec339941 Make codacy really happy. 2023-08-11 12:58:33 +02:00
4ac994979b Make codacy happy. 2023-08-11 12:55:43 +02:00
6972225e26 Prevent unindented reboots. 2023-08-11 12:49:48 +02:00
3c5b7334a8 Add/update example configurations. 2023-08-11 12:22:26 +02:00
40a005329f Handle all image variants. 2023-08-11 12:21:50 +02:00
3966cbabe4 Operator takes precedence. 2023-08-11 12:12:36 +02:00
59e595a1fa Handle CRD independently. 2023-08-11 10:21:49 +02:00
74e7d779b2 Adapt to file name change. 2023-08-11 10:17:25 +02:00
02b5cd588d Make role and binding namespaced. 2023-08-11 10:12:13 +02:00
c65c0f979b Remove left-over namespace properties. 2023-08-10 23:09:30 +02:00
6e377e0a56 Separate namespace for vm-operator development. 2023-08-10 22:01:28 +02:00
f729169082 Fix image URL. 2023-08-10 17:33:55 +02:00
63d94d3d96 Obtain namespace properly. 2023-08-10 17:28:06 +02:00
69c832dd56 Fix sample access mode. 2023-08-09 23:08:53 +02:00
3b72cb7969 Add sample files. 2023-08-09 22:56:00 +02:00
5d5b0c07c5 Use names with dash for k8s, but not for directories. 2023-08-09 21:56:30 +02:00
eea4afac72 Not needed. 2023-08-09 21:47:12 +02:00
7113aad8eb Use dash in k8s context, but not for directories. 2023-08-09 21:42:09 +02:00
ce07e43529 Adapt to latest conventions. 2023-08-09 18:24:57 +02:00
c4270caa7b Add container build. 2023-08-09 18:17:46 +02:00
f8281f6ef5 Updated root component name. 2023-08-09 18:17:30 +02:00
89fb5b1890 Fix file lookup. 2023-08-09 18:16:54 +02:00
050d283120 Don't cleanup PVCs. 2023-08-09 15:53:09 +02:00
4058fa6bda Use statefulset for runner. 2023-08-09 15:19:29 +02:00
b79ddcf05c Fix comment. 2023-08-09 13:41:19 +02:00
cc77a81f8e Rename methods. 2023-08-08 20:14:55 +02:00
65306f27b3 Adjust grace period to powerdown timeout. 2023-08-08 19:00:15 +02:00
093c6cf1d0 Fix image name evaluation. 2023-08-08 14:22:01 +02:00
85dca0cb80 Rename. 2023-08-08 14:18:17 +02:00
010b6fdce6 Rename event. 2023-08-08 13:22:27 +02:00
0c4cb7e9b4 Improve handling of missing (illegal) configuration. 2023-08-08 13:06:32 +02:00
d74ec52441 Better error message. 2023-08-08 13:05:56 +02:00
758412673e Improve powerdown behavior. 2023-08-08 12:42:44 +02:00
3a0da6cb70 No changes while terminating. 2023-08-08 10:25:12 +02:00
b76e5b1da9 "Normalize" (should read back as provided). 2023-08-07 20:37:34 +02:00
62c1bb9f42 Back to using null (didn't fix argocd problem). 2023-08-07 18:42:13 +02:00
5ec9233ac7 Syntax enhancements. 2023-08-07 18:31:27 +02:00
c0fbf06b54 Move "oneof"s before properties. 2023-08-07 18:28:02 +02:00
a28ccfc8e0 Add support for CDROM drives. 2023-08-07 17:26:16 +02:00
cc7037be0b Re-order methods. 2023-08-07 12:05:12 +02:00
6ac60d72f0 Fix toString. 2023-08-07 12:04:08 +02:00
37e8f90cd1 Use one event processor for "main loop" events. 2023-08-07 11:58:18 +02:00
3e43d02931 Javadoc fixes. 2023-08-07 10:28:47 +02:00
a2f00eb1ec Not needed. 2023-08-07 10:09:53 +02:00
fbc25d4db8 Refactor. 2023-08-06 23:01:50 +02:00
e8b10b32b0 Support cdrom media change. 2023-08-05 11:40:36 +02:00
ae2e6cde7f Add common image repository. 2023-08-04 14:26:58 +02:00
e6a2fced94 Moved. 2023-08-04 14:26:20 +02:00
96a5556939 Renamed for consistency. 2023-08-04 11:23:33 +02:00
e1c710d473 Fix NPE. 2023-08-04 10:21:32 +02:00
134e4c02f4 Make recovery work. 2023-08-04 10:21:22 +02:00
476d2cf84f Cleanup left over VMs on start. 2023-08-03 18:46:03 +02:00
2f101f2acc Support more than one CRD version. 2023-08-03 15:58:59 +02:00
fc60c3edf1 Refactor. 2023-08-03 12:16:06 +02:00
f3e6384db6 Basic start/stop functionality. 2023-08-03 11:36:23 +02:00
d5e4d87b1c Fix status definition. 2023-08-03 11:01:15 +02:00
abe43b86d9 Towards a generic example. 2023-08-02 14:07:37 +02:00
37ffe475fb Use updated library. 2023-08-02 12:51:08 +02:00
e3dbe372bd Handle startup problems. 2023-08-01 12:59:05 +02:00
d208917ab7 Log version information. 2023-08-01 12:54:04 +02:00
7a3ae1d381 Add version information. 2023-08-01 12:25:37 +02:00
e1b52da239 Add debug message. 2023-07-31 23:02:40 +02:00
ba5dad1fe5 Fix command. 2023-07-31 22:59:09 +02:00
ca1e00d326 Javadoc fixes. 2023-07-31 18:05:27 +02:00
d551d942eb Merge branch 'main' of git@github.com:mnlipp/VM-Operator.git into main 2023-07-31 18:00:16 +02:00
246e6480db Support for updating CPUs and RAM. 2023-07-31 17:59:56 +02:00
eac113c640 Support for updateing CPUs and RAM. 2023-07-31 17:59:16 +02:00
fa759df107 See https://github.com/checkstyle/checkstyle/issues/3320 2023-07-31 17:42:44 +02:00
9fd2e282b1 RAM size adaption works. 2023-07-30 12:31:23 +02:00
6be7239d49 Trigger immediate update. 2023-07-28 22:12:50 +02:00
7ebc4f50b3 Better name. 2023-07-28 18:19:30 +02:00
838bd1bb5e Better name for runner pvc. 2023-07-28 18:01:37 +02:00
7fdec42905 Fix using named pvcs. 2023-07-28 17:50:34 +02:00
dc1962af79 Working creation and removal. 2023-07-28 15:43:50 +02:00
954780e40f Start pod. 2023-07-27 18:47:09 +02:00
7fb1cc95af Add configuration. 2023-07-27 18:47:00 +02:00
48d8e63c25 Add dependencies. 2023-07-27 18:46:17 +02:00
fae89352a7 Update. 2023-07-27 18:45:50 +02:00
7655b7ab1a Rename templates, change model. 2023-07-27 18:45:37 +02:00
dd8407fb2d Change operator name. 2023-07-27 18:43:49 +02:00
870d63eb29 Add dependency on freemarker. 2023-07-27 13:17:32 +02:00
076f86bbe4 Create ConfigMap. 2023-07-27 13:14:04 +02:00
ee1a460960 Add status. 2023-07-26 13:14:30 +02:00
dae0688b98 Fix warning. 2023-07-25 12:54:31 +02:00
eb6960969d Rename method. 2023-07-25 12:53:35 +02:00
8f5bec1311 Separate application (Operator) from controller. 2023-07-25 12:44:45 +02:00
c74d48f8af Not using extended API. 2023-07-25 12:28:49 +02:00
d1410183cb Working PVC for disks reconciliation. 2023-07-25 12:26:42 +02:00
f1b1b2c059 Choose suitable API. 2023-07-23 17:13:58 +02:00
ddf412302b Fix copyright. 2023-07-22 22:31:41 +02:00
43395ce929 Rename. 2023-07-22 15:50:17 +02:00
50bff5d38f Implement basic reconciliation "loop". 2023-07-22 14:36:42 +02:00
de17d323c3 Support Spice ticket. 2023-06-20 13:29:24 +02:00
a2e9cd4f70 Support Spice ticket. 2023-06-20 12:00:02 +02:00
12c2601b92 Cleanup on shutdown. 2023-06-19 12:55:28 +02:00
d60f37e0fe Be more restrictive with file permissions. 2023-06-19 11:59:54 +02:00
488e6fafdc Support more display/spice options. 2023-06-18 23:04:57 +02:00
0879e40213 Getting started with k8s API. 2023-06-18 18:06:17 +02:00
8905e30473 Update settings. 2023-06-18 18:05:06 +02:00
23ca2e0e43 Handle bridge name correctly. 2023-06-18 14:38:53 +02:00
080156157f Prevent NPE when start fails. 2023-06-18 13:28:44 +02:00
0f3e185140 Provide history for properly generated versions. 2023-06-15 15:44:09 +02:00
5928371fc9 Use same logging configuration approach as with runner. 2023-06-13 12:16:59 +02:00
0669aa3daa Automatic driver selection for disks. 2023-06-13 11:28:21 +02:00
382 changed files with 48552 additions and 1968 deletions

14
.editorconfig Normal file
View file

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

16
.eslintrc.json Normal file
View file

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

View file

@ -2,7 +2,7 @@ name: "CodeQL"
on:
push:
branches: [ "main" ]
branches: [ "*" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]

View file

@ -1,10 +1,8 @@
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
push: {}
pull_request: {}
permissions:
contents: read
@ -20,12 +18,14 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install graphviz
run: sudo apt-get install graphviz
- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '17'
java-version: '21'
distribution: 'temurin'
- name: Build with Gradle
run: ./gradlew -Prepo.access.token=${{ secrets.REPO_ACCESS_TOKEN }} stage
run: ./gradlew -Pwebsite.push.token=${{ secrets.WEBSITE_PUSH_TOKEN }} stage

89
.github/workflows/jekyll.yml vendored Normal file
View file

@ -0,0 +1,89 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
name: Deploy Jekyll site to Pages
on:
# Runs on pushes targeting the default branch
push:
branches: ["main"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between
# the run in-progress and latest queued. However, do NOT cancel
# in-progress runs as we want to allow these production deployments
# to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3' # Not needed with a .ruby-version file
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
cache-version: 0 # Increment this number if you need to re-download cached gems
working-directory: webpages
- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
- name: Build with Jekyll
# Outputs to the './_site' directory by default
run: cd webpages && bundle exec jekyll build
env:
JEKYLL_ENV: production
- name: Install graphviz
run: sudo apt-get install graphviz
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
- name: Build apidocs
run: ./gradlew apidocs
- name: Copy javadoc
run: cp -a build/javadoc webpages/_site/
- name: Generate the sitemap
uses: cicirello/generate-sitemap@v1
with:
path-to-root: webpages/_site
base-url-path: https://vm-operator.jdrupes.org
- name: Index pagefind
run: cd webpages && npx pagefind --source "_site"
- name: Upload artifact
# Automatically uploads an artifact from the './_site' directory by default
uses: actions/upload-pages-artifact@v3
with:
path: './webpages/_site'
# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

View file

@ -8,13 +8,17 @@ permissions:
contents: read
packages: write
concurrency:
group: doc_generation
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install graphviz
@ -27,10 +31,10 @@ jobs:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up JDK 17
- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '17'
java-version: '21'
distribution: 'temurin'
- name: Push with Gradle
run: ./gradlew -Pdocker.registry=ghcr.io/${{ github.actor }} pushContainer
run: ./gradlew -Pwebsite.push.token=${{ secrets.WEBSITE_PUSH_TOKEN }} -Pdocker.registry=ghcr.io/${{ github.actor }} stage publishImage

4
.gitignore vendored
View file

@ -9,3 +9,7 @@ bin
.classpath
.project
org.eclipse.jdt.core.prefs
**/.externalToolBuilders/
# Node
**/node_modules/

30
.markdownlint.yaml Normal file
View file

@ -0,0 +1,30 @@
# See [rules](https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.yaml)
# Default state for all rules
default: true
# MD007/ul-indent : Unordered list indentation :
# https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md007.md
MD007:
# Spaces for indent
indent: 2
# Whether to indent the first level of the list
start_indented: true
# Spaces for first level indent (when start_indented is set)
start_indent: 2
# MD025/single-title/single-h1 : Multiple top-level headings in the same document :
# https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md025.md
MD025:
# Heading level
level: 1
# RegExp for matching title in front matter (disable)
front_matter_title: ""
# MD036/no-emphasis-as-heading : Emphasis used instead of a heading :
# https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md036.md
MD036: false
# MD043/required-headings : Required heading structure :
# https://github.com/DavidAnson/markdownlint/blob/v0.37.4/doc/md043.md
MD043: false

View file

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>VM-Operator</name>
<comment></comment>
<projects/>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
<nature>net.sf.eclipsecs.core.CheckstyleNature</nature>
<nature>ch.acanda.eclipse.pmd.builder.PMDNature</nature>
</natures>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments/>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments/>
</buildCommand>
<buildCommand>
<name>net.sf.eclipsecs.core.CheckstyleBuilder</name>
<arguments/>
</buildCommand>
<buildCommand>
<name>ch.acanda.eclipse.pmd.builder.PMDBuilder</name>
<arguments/>
</buildCommand>
</buildSpec>
<linkedResources/>
<filteredResources>
<filter>
<id>1</id>
<type>30</type>
<name/>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription>

View file

@ -1,11 +1,11 @@
arguments=--init-script /home/mnl/.config/Code/User/globalStorage/redhat.java/1.18.0/config_linux/org.eclipse.osgi/51/0/.cp/gradle/init/init.gradle --init-script /home/mnl/.config/Code/User/globalStorage/redhat.java/1.18.0/config_linux/org.eclipse.osgi/51/0/.cp/gradle/protobuf/init.gradle
arguments=--init-script /home/mnl/.config/Code/User/globalStorage/redhat.java/1.24.0/config_linux/org.eclipse.osgi/55/0/.cp/gradle/init/init.gradle --init-script /home/mnl/.config/Code/User/globalStorage/redhat.java/1.24.0/config_linux/org.eclipse.osgi/55/0/.cp/gradle/protobuf/init.gradle
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir=
eclipse.preferences.version=1
gradle.user.home=
java.home=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.362.b09-2.fc37.x86_64
java.home=
jvm.arguments=
offline.mode=false
override.workspace.settings=true

2
.vscode/launch.json vendored
View file

@ -24,7 +24,7 @@
"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.config.file=jul-debug.properties -Dorg.jdrupes.vmoperator.runner.qemu.config=./config.yaml"
"vmArgs": "-ea -Djava.util.logging.manager=org.jdrupes.vmoperator.util.LongLoggingManager"
}
]
}

38
.woodpecker/build.yaml Normal file
View file

@ -0,0 +1,38 @@
when:
- event: push
evaluate: 'CI_SYSTEM_HOST == "woodpecker.mnl.de"'
clone:
- name: git
image: woodpeckerci/plugin-git
settings:
partial: false
tags: true
depth: 0
steps:
- name: prepare
image: alpine
commands:
# Because we run the next step as user 1000 to make podman work:
- mkdir /woodpecker/workflow
- chown 1000:1000 /woodpecker/workflow
- chown -R 1000:1000 $CI_WORKSPACE
- name: build-jars
image: registry.mnl.de/mnl/jdk21-builder:v4
environment:
HOME: /woodpecker/workflow
REGISTRY: registry.mnl.de
REGISTRY_USER: mnl
REGISTRY_TOKEN:
from_secret: REGISTRY_TOKEN
commands:
- echo $REGISTRY_TOKEN | podman login -u $REGISTRY_USER --password-stdin $REGISTRY
- ./gradlew -Pdocker.registry=$REGISTRY/$REGISTRY_USER build apidocs publishImage
backend_options:
kubernetes:
securityContext:
privileged: true
runAsUser: 1000
runAsGroup: 1000

View file

@ -1,11 +1,25 @@
[![Java CI with Gradle](https://github.com/mnlipp/VM-Operator/actions/workflows/gradle.yml/badge.svg)](https://github.com/mnlipp/VM-Operator/actions/workflows/gradle.yml)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/2277842dac894de4b663c6aa2779077e)](https://app.codacy.com/gh/mnlipp/VM-Operator/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
![Latest Manager](https://img.shields.io/github/v/tag/mnlipp/vm-operator?filter=manager*&label=latest)
![Latest Runner](https://img.shields.io/github/v/tag/mnlipp/vm-operator?filter=runner-qemu*&label=latest)
# Run Qemu in Kubernetes Pods
# Run QEMU/KVM in Kubernetes Pods
The goal of this project is to provide the means for running Qemu
based VMs in Kubernetes pods.
![Overview picture](webpages/index-pic.svg)
See the [project's home page](https://mnlipp.github.io/VM-Operator/)
This project provides an easy to use and flexible solution for running
QEMU/KVM based VMs in Kubernetes pods.
The central component of this solution is the kubernetes operator that
manages "runners". These run in pods and are used to start and manage
the QEMU/KVM process for the VMs (optionally together with a SW-TPM).
A web GUI for administrators provides an overview of the VMs together
with some basic control over the VMs. A web GUI for users provides an
interface to access and optionally start, stop and reset the VMs.
Advanced features of the operator include pooling of VMs and automatic
login.
See the [project's home page](https://vm-operator.jdrupes.org/)
for details.

BIN
VM-Operator.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -2,22 +2,24 @@ buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath 'org.ajoberstar.grgit:grgit-gradle:4.1.0'
classpath 'org.ajoberstar:gradle-git-publish:3.0.0'
}
}
plugins {
id 'pl.allegro.tech.build.axion-release' version '1.15.0' apply false
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, build and update JavaDoc.'
description = 'To be executed by CI.'
group = 'build'
// Build everything first
@ -25,11 +27,6 @@ task stage {
dependsOn subprojects.tasks.collect {
tc -> tc.findByName("build") }.flatten()
}
if (JavaVersion.current() == JavaVersion.VERSION_17) {
// Publish JavaDoc
dependsOn gitPublishPush
}
}
eclipse {

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>buildSrc</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
</natures>
</projectDescription>

View file

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

View file

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

View file

@ -1,24 +1,13 @@
#
#Sun May 07 20:03:15 CEST 2023
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.nullReference=warning
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
#Wed Oct 02 14:48:43 CEST 2024
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
org.eclipse.jdt.core.compiler.source=17
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.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.compliance=17
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 +1,3 @@
eclipse.preferences.version=1
groovy.compiler.level=40
groovy.compiler.level=-1
groovy.script.filters=**/*.dsld,y,**/*.gradle,n

View file

@ -1,19 +1,11 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This project uses @Incubating APIs which are subject to change.
*/
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'
}
repositories {
// Use the plugin portal to apply community plugins in convention plugins.
gradlePluginPortal()
// Apply eclipse plugin
id 'eclipse'
}
sourceSets {
@ -21,19 +13,22 @@ sourceSets {
groovy {
srcDirs = ['src']
}
}
test {
groovy {
srcDirs = ['test']
resources {
srcDirs = ['resources']
}
}
}
/*
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
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,7 +0,0 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This settings file is used to specify which projects to include in your build-logic build.
*/
rootProject.name = 'buildSrc'

View file

@ -5,20 +5,24 @@
*/
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'
// Git based versioning
id 'pl.allegro.tech.build.axion-release'
// Apply eclipse plugin
id 'eclipse'
// Access to git information
id 'org.ajoberstar.grgit'
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
mavenLocal()
}
dependencies {
@ -51,20 +55,29 @@ sourceSets {
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
languageVersion = JavaLanguageVersion.of(21)
}
}
scmVersion {
versionIncrementer 'incrementMinor'
tag {
def shortened = project.name.startsWith(project.group + ".") ?
project.name.substring(project.group.length() + 1) : project.name
prefix = shortened.replace('.', '-') + "-"
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,
])
}
}
version = scmVersion.version
ext.isSnapshot = version.endsWith('-SNAPSHOT')
eclipse {

View file

@ -1,10 +1,15 @@
plugins {
// Apply the common convention plugin for shared build configuration between library and application projects.
id 'org.jdrupes.vmoperator.java-common-conventions'
// Apply the common versioning conventions.
id 'org.jdrupes.vmoperator.versioning-conventions'
id 'org.ajoberstar.git-publish'
}
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}
var docDestinationDir = file("${rootProject.buildDir}/javadoc")
configurations {
@ -17,31 +22,28 @@ configurations {
}
dependencies {
markdownDoclet "org.jdrupes.mdoclet:doclet:3.1.0"
javadocTaglets "org.jdrupes.taglets:plantuml-taglet:2.1.0"
markdownDoclet "org.jdrupes.mdoclet:doclet:4.0.0"
javadocTaglets "org.jdrupes.taglets:plantuml-taglet:3.0.0"
}
task javadocResources(type: Copy) {
into file(docDestinationDir)
from ("${rootProject.rootDir}/misc") {
include '*.woff2'
}
}
task java11doc (type: JavaExec) {
task apidocs (type: JavaExec) {
// Does not work on JitPack, no /usr/bin/dot
enabled = JavaVersion.current() == JavaVersion.VERSION_17
dependsOn javadocResources
enabled = JavaVersion.current() == JavaVersion.VERSION_21
outputs.dir(docDestinationDir)
inputs.file rootProject.file('overview.md')
inputs.file "${rootProject.rootDir}/misc/stylesheet.css"
inputs.file "${rootProject.rootDir}/misc/javadoc-overwrites.css"
jvmArgs = ['--add-exports=jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED']
main = 'jdk.javadoc.internal.tool.Main'
jvmArgs = ['--add-exports=jdk.compiler/com.sun.tools.doclint=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
'--add-exports=jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
'--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit=ALL-UNNAMED',
'--add-opens=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit.resources.releases=ALL-UNNAMED',
'-Duser.language=en', '-Duser.region=US']
mainClass = 'jdk.javadoc.internal.tool.Main'
gradle.projectsEvaluated {
// Make sure that other projects' compileClasspaths are resolved
@ -61,10 +63,11 @@ task java11doc (type: JavaExec) {
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/17/docs/api/',
'-link', 'https://mnlipp.github.io/jgrapes/latest-release/javadoc/',
'-link', 'https://docs.oracle.com/en/java/javase/21/docs/api/',
'-link', 'https://jgrapes.org/latest-release/javadoc/',
'-link', 'https://freemarker.apache.org/docs/api/',
'--add-exports', 'jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
'--add-exports', 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
@ -82,7 +85,7 @@ task java11doc (type: JavaExec) {
'-bottom', rootProject.file("misc/javadoc.bottom.txt").text,
'--allow-script-in-comments',
'-Xdoclint:-html',
'--main-stylesheet', "${rootProject.rootDir}/misc/stylesheet.css",
'--add-stylesheet', "${rootProject.rootDir}/misc/javadoc-overwrites.css",
'--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.formats.html=ALL-UNNAMED',
'-quiet'
]
@ -91,34 +94,27 @@ task java11doc (type: JavaExec) {
ignoreExitValue true
}
task testJavadoc(type: Javadoc) {
enabled = JavaVersion.current() == JavaVersion.VERSION_21
source = fileTree(dir: 'testfiles', include: '**/*.java')
destinationDir = project.file("build/testfiles-gradle")
options.docletpath = configurations.markdownDoclet.files.asType(List)
options.doclet = 'org.jdrupes.mdoclet.MDoclet'
options.overview = 'testfiles/overview.md'
options.addStringOption('Xdoclint:-html', '-quiet')
options.setJFlags([
'--add-exports=jdk.compiler/com.sun.tools.doclint=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
'--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
'--add-exports=jdk.javadoc/jdk.javadoc.internal.tool=ALL-UNNAMED',
'--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit=ALL-UNNAMED',
'--add-opens=jdk.javadoc/jdk.javadoc.internal.doclets.toolkit.resources.releases=ALL-UNNAMED'])
}
// Prepare github authentication for plugins
if (System.properties['org.ajoberstar.grgit.auth.username'] == null) {
System.setProperty('org.ajoberstar.grgit.auth.username',
project.rootProject.properties['repo.access.token'] ?: "nouser")
}
gitPublish {
repoUri = 'https://github.com/mnlipp/VM-Operator.git'
branch = 'gh-pages'
contents {
from("${rootProject.buildDir}/javadoc") {
into 'javadoc'
}
if ({ !findProject(':org.jdrupes.vmoperator.runner.qemu').isSnapshot
&& !findProject(':org.jdrupes.vmoperator.manager').isSnapshot }) {
from("${rootProject.buildDir}/javadoc") {
into 'latest-release/javadoc'
}
}
}
preserve { include '**/*' }
commitMessage = "Updated."
}
gradle.projectsEvaluated {
tasks.gitPublishReset.mustRunAfter subprojects.tasks
.collect { tc -> tc.findByName("build") }.flatten()
tasks.gitPublishReset.mustRunAfter subprojects.tasks
.collect { tc -> tc.findByName("test") }.flatten()
tasks.gitPublishCopy.dependsOn java11doc
project.rootProject.properties['website.push.token'] ?: "nouser")
}

View file

@ -0,0 +1,36 @@
/*
* 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,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Moodle Tools Console
Copyright (C) 2022 Michael N. Lipp
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
@ -30,8 +30,11 @@
<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"/>
@ -50,10 +53,9 @@
<property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="NeedBraces"/>
<module name="RightCurly"/>
<module name="RightCurly">
<property name="option" value="alone"/>
<property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, STATIC_INIT, INSTANCE_INIT"/>
<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"/>
@ -104,7 +106,7 @@
<module name="NoFinalizer"/>
<module name="AbbreviationAsWordInName">
<property name="allowedAbbreviationLength" value="1"/>
<property name="allowedAbbreviations" value="IO,MX,KV"/>
<property name="allowedAbbreviations" value="IO,MX,KV,DTO"/>
<property name="ignoreFinal" value="false"/>
</module>
<module name="OverloadMethodsDeclarationOrder"/>

View file

@ -0,0 +1,74 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: vmpools.vmoperator.jdrupes.org
spec:
group: vmoperator.jdrupes.org
# list of versions supported by this CustomResourceDefinition
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
retention:
description: >-
Defines the timeout for assignments. The time may be
specified as ISO 8601 time or duration. When specifying
a duration, it will be added to the last time the VM's
console was used to obtain the timeout.
type: string
pattern: '^(?:\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d{1,9})?(?:Z|[+-](?:[01]\d|2[0-3])(?:|:?[0-5]\d))|P(?:\d+Y)?(?:\d+M)?(?:\d+W)?(?:\d+D)?(?:T(?:\d+[Hh])?(?:\d+[Mm])?(?:\d+(?:\.\d{1,9})?[Ss])?)?)$'
default: "PT1h"
loginOnAssignment:
description: >-
If set to true, the user will be automatically logged in
to the VM's console when the VM is assigned to him.
type: boolean
default: false
permissions:
type: array
description: >-
Defines permissions for accessing and manipulating the Pool.
items:
type: object
description: >-
Permissions can be granted to a user or to a role.
oneOf:
- required:
- user
- required:
- role
properties:
user:
type: string
role:
type: string
may:
type: array
items:
type: string
enum:
- start
- stop
- reset
- accessConsole
- "*"
default: ["accessConsole"]
required:
- permissions
# either Namespaced or Cluster
scope: Namespaced
names:
# plural name to be used in the URL: /apis/<group>/<version>/<plural>
plural: vmpools
# singular name to be used as an alias on the CLI and for display
singular: vmpool
# kind is normally the CamelCased singular type. Your resource manifests use this.
kind: VmPool
listKind: VmPoolList

1632
deploy/crds/vms-crd.yaml Normal file

File diff suppressed because it is too large Load diff

14
deploy/kustomization.yaml Normal file
View file

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

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

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

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

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

46
deploy/vmop-role.yaml Normal file
View file

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

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

12
deploy/vmop-service.yaml Normal file
View file

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

View file

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

27
deploy/vmrunner-role.yaml Normal file
View file

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

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

4
dev-example/.gitignore vendored Normal file
View file

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

19
dev-example/Readme.md Normal file
View file

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

90
dev-example/config.yaml Normal file
View file

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

47
dev-example/gen-pool-vm-crds Executable file
View file

@ -0,0 +1,47 @@
#!/bin/bash
function usage() {
cat >&2 <<EOF
Usage: $0 [OPTION]... [TEMPLATE]
Generate VM CRDs using TEMPLATE.
-c, --count Count of VMs to generate
-d, --destination DIR Generate into given directory (default: ".")
-h, --help Print this help
-p, --prefix PREFIX Prefix for generated file (default: basename of template)
EOF
exit 1
}
count=0
destination=.
template=""
prefix=""
while [ "$#" -gt 0 ]; do
case "$1" in
-c|--count) shift; count=$1;;
-d|--destination) shift; destination="$1";;
-h|--help) shift; usage;;
-p|--prefix) shift; prefix="$1";;
-*) echo >&2 "Unknown option: $1"; exit 1;;
*) template="$1";;
esac
shift
done
if [ -z "$template" ]; then
usage
fi
if [ "$count" = "0" ]; then
exit 0
fi
for number in $(seq 1 $count); do
if [ -z "$prefix" ]; then
prefix=$(basename $template .tpl.yaml)
fi
name="$prefix$(printf %03d $number)"
index=$(($number - 1))
esh -o $destination/$name.yaml $template number=$number index=$index
done

View file

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

66
dev-example/pool-action Executable file
View file

@ -0,0 +1,66 @@
#!/bin/bash
function usage() {
cat >&2 <<EOF
Usage: $0 pool-name action
Applys action to all VMs in the pool.
--context Context to be passed to kubectl (required)
-n, --namespace Namespace to be passed to kubectl
Action is one of "start", "stop", "delete" or "delete-disks"
Defaults for context and namespace are read from .vm-operator-cmd.rc.
EOF
exit 1
}
unset pool
unset action
unset context
namespace=default
if [ -r .vm-operator-cmd.rc ]; then
. .vm-operator-cmd.rc
fi
while [ "$#" -gt 0 ]; do
case "$1" in
--context) shift; context="$1";;
--context=*) IFS='=' read -r option value <<< "$1"; context="$value";;
-n|--namespace) shift; namespace="$1";;
-*) echo >&2 "Unknown option: $1"; exit 1;;
*) if [ ! -v pool ]; then
pool="$1"
elif [ ! -v action ]; then
action="$1"
else
usage
fi;;
esac
shift
done
if [ ! -v pool -o ! -v "action" -o ! -v context ]; then
echo >&2 "Missing arguments or context not set."
echo >&2
usage
fi
case "$action" in
"start"|"stop"|"delete"|"delete-disks") ;;
*) usage;;
esac
kubectl --context="$context" -n "$namespace" get vms -o json \
| jq -r '.items[] | select(.spec.pools | contains(["'${pool}'"])) | .metadata.name' \
| while read vmName; do
case "$action" in
start) kubectl --context="$context" -n "$namespace" patch vms "$vmName" \
--type='merge' -p '{"spec":{"vm":{"state":"Running"}}}';;
stop) kubectl --context="$context" -n "$namespace" patch vms "$vmName" \
--type='merge' -p '{"spec":{"vm":{"state":"Stopped"}}}';;
delete) kubectl --context="$context" -n "$namespace" delete vm/"$vmName";;
delete-disks) kubectl --context="$context" -n "$namespace" delete \
pvc -l app.kubernetes.io/instance="$vmName" ;;
esac
done

View file

@ -0,0 +1,17 @@
apiVersion: "vmoperator.jdrupes.org/v1"
kind: VmPool
metadata:
namespace: vmop-dev
name: test-vms
spec:
retention: "PT1m"
loginOnAssignment: true
permissions:
- user: admin
may:
- accessConsole
- start
- role: user
may:
- accessConsole
- start

View file

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

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

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

View file

@ -0,0 +1,10 @@
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
namespace: vmop-dev
name: test-vm-system-disk-snapshot
spec:
volumeSnapshotClassName: csi-rbdplugin-snapclass
source:
persistentVolumeClaimName: test-vm-system-disk

View file

@ -0,0 +1,66 @@
apiVersion: "vmoperator.jdrupes.org/v1"
kind: VirtualMachine
metadata:
namespace: vmop-dev
name: test-vm<%= $(printf "%02d" ${number}) %>
annotations:
argocd.argoproj.io/sync-wave: "20"
spec:
image:
source: ghcr.io/mnlipp/org.jdrupes.vmoperator.runner.qemu-arch:latest
# source: registry.mnl.de/org/jdrupes/vm-operator/org.jdrupes.vmoperator.runner.qemu-arch:testing
# source: docker-registry.lan.mnl.de/vmoperator/org.jdrupes.vmoperator.runner.qemu-arch:latest
pullPolicy: Always
runnerTemplate:
update: true
permissions:
- role: admin
may:
- "*"
guestShutdownStops: true
cloudInit:
metaData: {}
pools:
- test-vms
vm:
# state: Running
bootMenu: true
maximumCpus: 4
currentCpus: 2
maximumRam: 6Gi
currentRam: 4Gi
networks:
# No bridge on TC1
# - tap: {}
- user: {}
disks:
- volumeClaimTemplate:
metadata:
name: system
spec:
storageClassName: ceph-rbd3slow
dataSource:
name: test-vm-system-disk-snapshot
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 40Gi
- cdrom:
image: ""
# image: https://download.fedoraproject.org/pub/fedora/linux/releases/38/Workstation/x86_64/iso/Fedora-Workstation-Live-x86_64-38-1.6.iso
display:
spice:
port: <%= $((5910 + number)) %>

62
dev-example/test-vm.yaml Normal file
View file

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

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

View file

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

146
dev-example/vmop-agent/vmop-agent Executable file
View file

@ -0,0 +1,146 @@
#!/usr/bin/bash
# Note that this script requires "jq" to be installed and a version
# of loginctl that accepts the "-j" option.
while [ "$#" -gt 0 ]; do
case "$1" in
--path) shift; ttyPath="$1";;
--path=*) IFS='=' read -r option value <<< "$1"; ttyPath="$value";;
esac
shift
done
ttyPath="${ttyPath:-/dev/virtio-ports/org.jdrupes.vmop_agent.0}"
if [ ! -w "$ttyPath" ]; then
echo >&2 "Device $ttyPath not writable"
exit 1
fi
# Create fd for the tty in variable con
if ! exec {con}<>"$ttyPath"; then
echo >&2 "Cannot open device $ttyPath"
exit 1
fi
# Temporary file for logging error messages, clear tty and signal ready
temperr=$(mktemp)
clear >/dev/tty1
echo >&${con} "220 Hello"
# This script uses the (shared) home directory as "dictonary" for
# synchronizing the username and the uid between hosts.
#
# Every user has a directory with his username. The directory is
# owned by root to prevent changes of access rights by the user.
# The uid and gid of the directory are equal. Thus the name of the
# directory and the id from the group ownership also provide the
# association between the username and the uid.
# Add the user with name $1 to the host's "user database". This
# may not be invoked concurrently.
createUser() {
local missing=$1
local uid
local userHome="/home/$missing"
local createOpts=""
# Retrieve or create the uid for the username
if [ -d "$userHome" ]; then
# If a home directory exists, use the id from the group ownership as uid
uid=$(ls -ldn "$userHome" | head -n 1 | awk '{print $4}')
createOpts="--no-create-home"
else
# Else get the maximum of all ids from the group ownership +1
uid=$(ls -ln "/home" | tail -n +2 | awk '{print $4}' | sort | tail -1)
uid=$(( $uid + 1 ))
if [ $uid -lt 1100 ]; then
uid=1100
fi
createOpts="--create-home"
fi
groupadd -g $uid $missing
useradd $missing -u $uid -g $uid $createOpts
}
# Login the user, i.e. create a desktopn for the user.
doLogin() {
user=$1
if [ "$user" = "root" ]; then
echo >&${con} "504 Won't log in root"
return
fi
# Check if this user is already logged in on tty2
curUser=$(loginctl -j | jq -r '.[] | select(.tty=="tty2") | .user')
if [ "$curUser" = "$user" ]; then
echo >&${con} "201 User already logged in"
return
fi
# Terminate a running desktop (fail safe)
attemptLogout
# Check if username is known on this host. If not, create user
uid=$(id -u ${user} 2>/dev/null)
if [ $? != 0 ]; then
( flock 200
createUser ${user}
) 200>/home/.gen-uid-lock
# This should now work, else something went wrong
uid=$(id -u ${user} 2>/dev/null)
if [ $? != 0 ]; then
echo >&${con} "451 Cannot determine uid"
return
fi
fi
# Configure user as auto login user
sed -i '/AutomaticLogin/d' /etc/gdm/custom.conf
sed -i '/\[daemon\]/a AutomaticLoginEnable=true\nAutomaticLogin='$user \
/etc/gdm/custom.conf
# Activate user
systemctl restart gdm
if [ $? -eq 0 ]; then
echo >&${con} "201 User logged in successfully"
else
echo >&${con} "451 $(tr '\n' ' ' <${temperr})"
fi
}
# Attempt to log out a user currently using tty1. This is an intermediate
# operation that can be invoked from other operations
attemptLogout() {
sed -i '/AutomaticLogin/d' /etc/gdm/custom.conf
systemctl stop gdm
echo >&${con} "102 Desktop stopped"
}
# Log out any user currently using tty1. This is invoked when executing
# the logout command and therefore sends back a 2xx return code.
# Also try to restart gdm, if it is not running.
doLogout() {
attemptLogout
systemctl restart gdm
echo >&${con} "202 User logged out"
}
while read line <&${con}; do
case $line in
"login "*) IFS=' ' read -ra args <<< "$line"; doLogin ${args[1]};;
"logout") doLogout;;
esac
done
onExit() {
doLogout
if [ -n "$temperr" ]; then
rm -f $temperr
fi
echo >&${con} "240 Quit"
}
trap onExit EXIT

View file

@ -0,0 +1,15 @@
[Unit]
Description=VM-Operator (Guest) Agent
BindsTo=dev-virtio\x2dports-org.jdrupes.vmop_agent.0.device
After=dev-virtio\x2dports-org.jdrupes.vmop_agent.0.device multi-user.target
IgnoreOnIsolate=True
[Service]
UMask=0077
#EnvironmentFile=/etc/sysconfig/vmop-agent
ExecStart=/usr/local/libexec/vmop-agent
Restart=always
RestartSec=0
[Install]
WantedBy=dev-virtio\x2dports-org.jdrupes.vmop_agent.0.device

View file

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

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

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

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

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

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

1
gradle.properties Normal file
View file

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

Binary file not shown.

View file

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

31
gradlew vendored
View file

@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -83,10 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -133,18 +131,21 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

20
gradlew.bat vendored
View file

@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

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

View file

@ -4,11 +4,33 @@
<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) {
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,912 +0,0 @@
/*
* Javadoc style sheet
*/
@font-face {
font-family: 'DejaVu Serif';
src: local('DejaVu Serif'), url('DejaVuSerif.woff2');
}
@font-face {
font-family: 'DejaVu Serif';
font-weight: bold;
src: local('DejaVu Serif Bold'), url('DejaVuSerif-Bold.woff2');
}
@font-face {
font-family: 'DejaVu Sans';
src: local('DejaVu Sans'), url('DejaVuSans.woff2');
}
@font-face {
font-family: 'DejaVu Sans';
font-weight: bold;
src: local('DejaVu Sans Bold'), url('DejaVuSans-Bold.woff2');
}
@font-face {
font-family: 'DejaVu Sans Mono';
src: local('DejaVu Sans Mono'), url('DejaVuSansMono.woff2');
}
@font-face {
font-family: 'DejaVu Sans Mono';
font-weight: bold;
src: local('DejaVu Sans Mono Bold'), url('DejaVuSansMono-Bold.woff2');
}
/*
* Styles for individual HTML elements.
*
* These are styles that are specific to individual HTML elements. Changing them affects the style of a particular
* HTML element throughout the page.
*/
body {
background-color:#ffffff;
color:#353833;
font: normal 16px/1.5 "DejaVu Serif", serif;
margin:0;
padding:0;
height:100%;
width:100%;
}
iframe {
margin:0;
padding:0;
height:100%;
width:100%;
overflow-y:scroll;
border:none;
}
a:link, a:visited {
text-decoration:none;
color:#4A6782;
}
a[href]:hover, a[href]:focus {
text-decoration:none;
color:#bb7a2a;
}
a[name] {
color:#353833;
}
pre {
font-family: "DejaVu Sans Mono", monospace;
}
h1 {
font-family: "DejaVu Sans", sans;
font-size:20px;
}
h2 {
font-family: "DejaVu Sans", sans;
font-size:18px;
}
h3 {
font-family: "DejaVu Sans", sans;
font-size:16px;
}
h4 {
font-family: "DejaVu Sans", sans;
font-size:15px;
}
h5 {
font-family: "DejaVu Sans", sans;
font-size:14px;
}
h6 {
font-family: "DejaVu Sans", sans;
font-size:13px;
}
ul {
list-style-type:disc;
}
code, tt {
font-family: "DejaVu Sans Mono", monospace;
}
:not(h1, h2, h3, h4, h5, h6) > code,
:not(h1, h2, h3, h4, h5, h6) > tt {
/* font-size:14px; */
padding-top:4px;
margin-top:8px;
line-height:1.4em;
}
dt code {
font-family: "DejaVu Sans Mono", monospace;
font-size:14px;
padding-top:4px;
}
.summary-table dt code {
font-family: "DejaVu Sans Mono", monospace;
font-size:14px;
vertical-align:top;
padding-top:4px;
}
sup {
font-size:8px;
}
/*
* Styles for HTML generated by javadoc.
*
* These are style classes that are used by the standard doclet to generate HTML documentation.
*/
/*
* Styles for document title and copyright.
*/
.clear {
clear:both;
height:0;
overflow:hidden;
}
.about-language {
float:right;
padding:0 21px 8px 8px;
font-size:11px;
margin-top:-9px;
height:2.9em;
}
.legal-copy {
margin-left:.5em;
}
.tab {
background-color:#0066FF;
color:#ffffff;
padding:8px;
width:5em;
font-weight:bold;
}
/*
* Styles for navigation bar.
*/
@media screen {
.flex-box {
position:fixed;
display:flex;
flex-direction:column;
height: 100%;
width: 100%;
}
.flex-header {
flex: 0 0 auto;
}
.flex-content {
flex: 1 1 auto;
overflow-y: auto;
}
}
.top-nav {
background-color:#4D7A97;
color:#FFFFFF;
float:left;
padding:0;
width:100%;
clear:right;
min-height:2.8em;
padding-top:10px;
overflow:hidden;
font-family: "DejaVu Sans", sans;
font-size:80%;
}
.sub-nav {
background-color:#dee3e9;
float:left;
width:100%;
overflow:hidden;
font-family: "DejaVu Sans", sans;
font-size:80%;
}
.sub-nav div {
clear:left;
float:left;
padding:0 0 5px 6px;
text-transform:uppercase;
}
.sub-nav .nav-list {
padding-top:5px;
}
ul.nav-list {
display:block;
margin:0 25px 0 0;
padding:0;
}
ul.sub-nav-list {
float:left;
margin:0 25px 0 0;
padding:0;
}
ul.nav-list li {
list-style:none;
float:left;
padding: 5px 6px;
text-transform:uppercase;
}
.sub-nav .nav-list-search {
float:right;
margin:0 0 0 0;
padding:5px 6px;
clear:none;
}
.nav-list-search label {
position:relative;
right:-16px;
}
ul.sub-nav-list li {
list-style:none;
float:left;
padding-top:10px;
}
.top-nav a:link, .top-nav a:active, .top-nav a:visited {
color:#FFFFFF;
text-decoration:none;
text-transform:uppercase;
}
.top-nav a:hover {
text-decoration:none;
color:#bb7a2a;
text-transform:uppercase;
}
.nav-bar-cell1-rev {
background-color:#F8981D;
color:#253441;
margin: auto 5px;
}
.skip-nav {
position:absolute;
top:auto;
left:-9999px;
overflow:hidden;
}
/*
* Hide navigation links and search box in print layout
*/
@media print {
ul.nav-list, div.sub-nav {
display:none;
}
}
/*
* Styles for page header and footer.
*/
.title {
color:#2c4557;
margin:10px 0;
}
.sub-title {
margin:5px 0 0 0;
}
.header ul {
margin:0 0 15px 0;
padding:0;
}
.header ul li, .footer ul li {
list-style:none;
font-size:80%;
}
/*
* Styles for headings.
*/
body.class-declaration-page .summary h2,
body.class-declaration-page .details h2,
body.class-use-page h2,
body.module-declaration-page .block-list h2 {
font-style: italic;
padding:0;
margin:15px 0;
}
body.class-declaration-page .summary h3,
body.class-declaration-page .details h3,
body.class-declaration-page .summary .inherited-list h2 {
background-color:#dee3e9;
border:1px solid #d0d9e0;
margin:0 0 6px -8px;
padding:7px 5px;
}
/*
* Styles for page layout containers.
*/
main {
clear:both;
padding:10px 20px;
position:relative;
}
dl.notes > dt {
font-family: "DejaVu Sans", sans;
font-weight:bold;
margin:10px 0 0 0;
color:#4E4E4E;
}
dl.notes > dd {
margin:5px 10px 10px 0;
}
dl.name-value > dt {
margin-left:1px;
/* font-size:1.1em; */
display:inline;
font-weight:bold;
}
dl.name-value > dd {
margin:0 0 0 1px;
/* font-size:1.1em; */
display:inline;
}
/*
* Styles for lists.
*/
li.circle {
list-style:circle;
}
ul.horizontal li {
display:inline;
/* font-size:0.9em; */
}
div.inheritance {
margin:0;
padding:0;
}
div.inheritance div.inheritance {
margin-left:2em;
}
ul.block-list,
ul.details-list,
ul.member-list,
ul.summary-list {
margin:10px 0 10px 0;
padding:0;
}
ul.block-list > li,
ul.details-list > li,
ul.member-list > li,
ul.summary-list > li {
list-style:none;
margin-bottom:15px;
line-height:1.4;
}
.summary-table dl, .summary-table dl dt, .summary-table dl dd {
margin-top:0;
margin-bottom:1px;
}
ul.see-list, ul.see-list-long {
padding-left: 0;
list-style: none;
}
ul.see-list li {
display: inline;
}
ul.see-list li:not(:last-child):after,
ul.see-list-long li:not(:last-child):after {
content: ", ";
white-space: pre-wrap;
}
/*
* Styles for tables.
*/
.summary-table, .details-table {
width:100%;
border-spacing:0;
border-left:1px solid #EEE;
border-right:1px solid #EEE;
border-bottom:1px solid #EEE;
padding:0;
}
.caption {
position:relative;
text-align:left;
background-repeat:no-repeat;
color:#253441;
font-weight:bold;
clear:none;
overflow:hidden;
padding:0;
padding-top:10px;
padding-left:1px;
margin:0;
white-space:pre;
font-family: 'DejaVu Sans';
}
.caption a:link, .caption a:visited {
color:#1f389c;
}
.caption a:hover,
.caption a:active {
color:#FFFFFF;
}
.caption span {
white-space:nowrap;
padding-top:5px;
padding-left:12px;
padding-right:12px;
padding-bottom:7px;
display:inline-block;
float:left;
background-color:#F8981D;
border: none;
height:16px;
}
div.table-tabs {
padding:10px 0 0 1px;
margin:0;
}
div.table-tabs > button {
border: none;
cursor: pointer;
padding: 5px 12px 7px 12px;
font-weight: bold;
margin-right: 3px;
}
div.table-tabs > button.active-table-tab {
background: #F8981D;
color: #253441;
}
div.table-tabs > button.table-tab {
background: #4D7A97;
color: #FFFFFF;
}
.two-column-summary {
display: grid;
grid-template-columns: minmax(15%, max-content) minmax(15%, auto);
}
.three-column-summary {
display: grid;
grid-template-columns: minmax(10%, max-content) minmax(15%, max-content) minmax(15%, auto);
}
#method-summary-table .three-column-summary {
grid-template-columns: minmax(10%, 20%) minmax(15%, max-content) minmax(15%, auto);
}
.four-column-summary {
display: grid;
grid-template-columns: minmax(10%, max-content) minmax(10%, max-content) minmax(10%, max-content) minmax(10%, auto);
}
@media screen and (max-width: 600px) {
.two-column-summary {
display: grid;
grid-template-columns: 1fr;
}
}
@media screen and (max-width: 800px) {
.three-column-summary {
display: grid;
grid-template-columns: minmax(10%, max-content) minmax(25%, auto);
}
.three-column-summary .col-last {
grid-column-end: span 2;
}
}
@media screen and (max-width: 1000px) {
.four-column-summary {
display: grid;
grid-template-columns: minmax(15%, max-content) minmax(15%, auto);
}
}
.summary-table > div, .details-table > div {
text-align:left;
padding: 8px 3px 3px 7px;
}
.col-first, .col-second, .col-last, .col-constructor-name, .col-summary-item-name {
vertical-align:top;
padding-right:0;
padding-top:8px;
padding-bottom:3px;
}
.table-header {
background:#dee3e9;
font-family: 'DejaVu Sans';
font-weight: bold;
}
/*
.col-first, .col-first {
font-size:13px;
}
.col-second, .col-second, .col-last, .col-constructor-name, .col-summary-item-name, .col-last {
font-size:13px;
}
*/
.col-first, .col-second, .col-constructor-name {
vertical-align:top;
overflow: auto;
}
.col-last {
white-space:normal;
}
/*
.col-first a:link, .col-first a:visited,
.col-second a:link, .col-second a:visited,
.col-first a:link, .col-first a:visited,
.col-second a:link, .col-second a:visited,
.col-constructor-name a:link, .col-constructor-name a:visited,
.col-summary-item-name a:link, .col-summary-item-name a:visited,
.constant-values-container a:link, .constant-values-container a:visited,
.all-classes-container a:link, .all-classes-container a:visited,
.all-packages-container a:link, .all-packages-container a:visited {
font-weight:bold;
}
*/
.table-sub-heading-color {
background-color:#EEEEFF;
}
.even-row-color, .even-row-color .table-header {
background-color:#FFFFFF;
}
.odd-row-color, .odd-row-color .table-header {
background-color:#EEEEEF;
}
/*
* Styles for contents.
*/
.deprecated-content {
margin:0;
padding:10px 0;
}
/*
div.block {
font-size:14px;
font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif;
}
*/
.col-last div {
padding-top:0;
}
.col-last a {
padding-bottom:3px;
}
.module-signature,
.package-signature,
.type-signature,
.member-signature {
font-family: "DejaVu Sans Mono", monospace;
/* font-size:14px; */
margin:14px 0;
white-space: pre-wrap;
}
.module-signature,
.package-signature,
.type-signature {
margin-top: 0;
}
.member-signature .type-parameters-long,
.member-signature .parameters,
.member-signature .exceptions {
display: inline-block;
vertical-align: top;
white-space: pre;
}
.member-signature .type-parameters {
white-space: normal;
}
/*
* Styles for formatting effect.
*/
.source-line-no {
color:green;
padding:0 30px 0 0;
}
h1.hidden {
visibility:hidden;
overflow:hidden;
/* font-size:10px; */
}
.block {
display:block;
margin:0 10px 5px 0;
color:#474747;
}
.deprecated-label, .descfrm-type-label, .implementation-label, .member-name-label, .member-name-link,
.module-label-in-package, .module-label-in-type, .override-specify-label, .package-label-in-type,
.package-hierarchy-label, .type-name-label, .type-name-link, .search-tag-link, .preview-label {
font-family: "DejaVu Sans", sans;
font-weight:bold;
}
.sub-title, .inheritance, .all-packages-table-tab1.col-first,
.summary-table .col-first {
font-family: "DejaVu Sans", sans;
}
.deprecation-comment, .help-footnote, .preview-comment {
font-style:italic;
}
.deprecation-block {
/* font-size:14px; */
font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif;
border-style:solid;
border-width:thin;
border-radius:10px;
padding:10px;
margin-bottom:10px;
margin-right:10px;
display:inline-block;
}
.preview-block {
/* font-size:14px; */
font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif;
border-style:solid;
border-width:thin;
border-radius:10px;
padding:10px;
margin-bottom:10px;
margin-right:10px;
display:inline-block;
}
div.block div.deprecation-comment {
font-style:normal;
}
/*
* Styles specific to HTML5 elements.
*/
main, nav, header, footer, section {
display:block;
}
/*
* Styles for javadoc search.
*/
.ui-autocomplete-category {
font-weight:bold;
/* font-size:15px; */
padding:7px 0 7px 3px;
background-color:#4D7A97;
color:#FFFFFF;
}
.result-item {
/* font-size:13px; */
}
.ui-autocomplete {
max-height:85%;
max-width:65%;
overflow-y:scroll;
overflow-x:scroll;
white-space:nowrap;
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
}
ul.ui-autocomplete {
position:fixed;
z-index:999999;
}
ul.ui-autocomplete li {
float:left;
clear:both;
width:100%;
}
.result-highlight {
font-weight:bold;
}
#search-input {
background-image:url('resources/glass.png');
background-size:13px;
background-repeat:no-repeat;
background-position:2px 3px;
padding-left:20px;
position:relative;
right:-18px;
width:400px;
}
#reset-button {
background-color: rgb(255,255,255);
background-image:url('resources/x.png');
background-position:center;
background-repeat:no-repeat;
background-size:12px;
border:0 none;
width:16px;
height:16px;
position:relative;
left:-4px;
top:-4px;
font-size:0px;
}
.watermark {
color:#545454;
}
.search-tag-desc-result {
font-style:italic;
/* font-size:11px; */
}
.search-tag-holder-result {
font-style:italic;
/* font-size:12px; */
}
.search-tag-result:target {
background-color:yellow;
}
.module-graph span {
display:none;
position:absolute;
}
.module-graph:hover span {
display:block;
margin: -100px 0 0 100px;
z-index: 1;
}
.inherited-list {
margin: 10px 0 10px 0;
}
section.class-description {
line-height: 1.4;
}
.summary section[class$="-summary"], .details section[class$="-details"],
.class-uses .detail, .serialized-class-details {
padding: 0px 20px 5px 10px;
border: 1px solid #ededed;
background-color: #f8f8f8;
}
.inherited-list, section[class$="-details"] .detail {
padding:0 0 5px 8px;
background-color:#ffffff;
border:none;
}
.vertical-separator {
padding: 0 5px;
}
ul.help-section-list {
margin: 0;
}
ul.help-subtoc > li {
display: inline-block;
padding-right: 5px;
/* font-size: smaller; */
}
ul.help-subtoc > li::before {
content: "\2022" ;
padding-right:2px;
}
span.help-note {
font-style: italic;
}
/*
* Indicator icon for external links.
*/
main a[href*="://"]::after {
content:"";
display:inline-block;
background-image:url('data:image/svg+xml; utf8, \
<svg xmlns="http://www.w3.org/2000/svg" width="768" height="768">\
<path d="M584 664H104V184h216V80H0v688h688V448H584zM384 0l132 \
132-240 240 120 120 240-240 132 132V0z" fill="%234a6782"/>\
</svg>');
background-size:100% 100%;
width:7px;
height:7px;
margin-left:2px;
margin-bottom:4px;
}
main a[href*="://"]:hover::after,
main a[href*="://"]:focus::after {
background-image:url('data:image/svg+xml; utf8, \
<svg xmlns="http://www.w3.org/2000/svg" width="768" height="768">\
<path d="M584 664H104V184h216V80H0v688h688V448H584zM384 0l132 \
132-240 240 120 120 240-240 132 132V0z" fill="%23bb7a2a"/>\
</svg>');
}
/*
* Styles for user-provided tables.
*
* borderless:
* No borders, vertical margins, styled caption.
* This style is provided for use with existing doc comments.
* In general, borderless tables should not be used for layout purposes.
*
* plain:
* Plain borders around table and cells, vertical margins, styled caption.
* Best for small tables or for complex tables for tables with cells that span
* rows and columns, when the "striped" style does not work well.
*
* striped:
* Borders around the table and vertical borders between cells, striped rows,
* vertical margins, styled caption.
* Best for tables that have a header row, and a body containing a series of simple rows.
*/
table.borderless,
table.plain,
table.striped {
margin-top: 10px;
margin-bottom: 10px;
}
table.borderless > caption,
table.plain > caption,
table.striped > caption {
font-weight: bold;
/* font-size: smaller; */
}
table.borderless th, table.borderless td,
table.plain th, table.plain td,
table.striped th, table.striped td {
padding: 2px 5px;
}
table.borderless,
table.borderless > thead > tr > th, table.borderless > tbody > tr > th, table.borderless > tr > th,
table.borderless > thead > tr > td, table.borderless > tbody > tr > td, table.borderless > tr > td {
border: none;
}
table.borderless > thead > tr, table.borderless > tbody > tr, table.borderless > tr {
background-color: transparent;
}
table.plain {
border-collapse: collapse;
border: 1px solid black;
}
table.plain > thead > tr, table.plain > tbody tr, table.plain > tr {
background-color: transparent;
}
table.plain > thead > tr > th, table.plain > tbody > tr > th, table.plain > tr > th,
table.plain > thead > tr > td, table.plain > tbody > tr > td, table.plain > tr > td {
border: 1px solid black;
}
table.striped {
border-collapse: collapse;
border: 1px solid black;
}
table.striped > thead {
background-color: #E3E3E3;
}
table.striped > thead > tr > th, table.striped > thead > tr > td {
border: 1px solid black;
}
table.striped > tbody > tr:nth-child(even) {
background-color: #EEE
}
table.striped > tbody > tr:nth-child(odd) {
background-color: #FFF
}
table.striped > tbody > tr > th, table.striped > tbody > tr > td {
border-left: 1px solid black;
border-right: 1px solid black;
}
table.striped > tbody > tr > th {
font-weight: normal;
}
/**
* Tweak font sizes and paddings for small screens.
*/
@media screen and (max-width: 1050px) {
#search-input {
width: 300px;
}
}
@media screen and (max-width: 800px) {
#search-input {
width: 200px;
}
.top-nav,
.bottom-nav {
font-size: 80%;
padding-top: 6px;
}
.sub-nav {
font-size: 80%;
}
.about-language {
padding-right: 16px;
}
ul.nav-list li,
.sub-nav .nav-list-search {
padding: 6px;
}
ul.sub-nav-list li {
padding-top: 5px;
}
main {
padding: 10px;
}
.summary section[class$="-summary"], .details section[class$="-details"],
.class-uses .detail, .serialized-class-details {
padding: 0 8px 5px 8px;
}
body {
-webkit-text-size-adjust: none;
}
}
@media screen and (max-width: 500px) {
#search-input {
width: 150px;
}
.top-nav,
.bottom-nav {
font-size: 80%;
}
.sub-nav {
font-size: 80%;
}
.about-language {
font-size: 80%;
padding-right: 12px;
}
}

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

@ -0,0 +1,127 @@
/*
* 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

@ -0,0 +1,113 @@
/*
* 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

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

@ -0,0 +1,249 @@
/*
* 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

@ -0,0 +1,954 @@
/*
* 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

@ -0,0 +1,396 @@
/*
* 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

@ -0,0 +1,113 @@
/*
* 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

@ -0,0 +1,44 @@
/*
* 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

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

@ -0,0 +1,152 @@
/*
* 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

@ -0,0 +1,49 @@
/*
* 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

@ -0,0 +1,474 @@
/*
* 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

@ -0,0 +1,237 @@
/*
* 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

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

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

@ -0,0 +1,83 @@
/*
* 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

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