Compare commits
62 commits
Author | SHA1 | Date | |
---|---|---|---|
78b238928b | |||
|
bef0fff652 | ||
|
8f27fde09c | ||
|
9630e23ede | ||
|
e8c0c97dbe | ||
|
72af2db838 | ||
|
bb9146b542 | ||
|
343c339a5a | ||
|
44f788eb08 | ||
|
41ab214a43 | ||
|
34c5101689 | ||
|
30d12734fb | ||
|
d31ffd07b7 | ||
|
09c6de5318 | ||
|
6f5560274d | ||
|
de7c1225b9 | ||
|
e14db18eb7 | ||
|
2ebe84d42a | ||
|
469f864339 | ||
277302d0ee | |||
43b7e83187 | |||
90d5a96ce8 | |||
4befbd1017 | |||
4bd18fcdf8 | |||
|
1b7e2824ce | ||
|
fd0440fc2f | ||
|
3217fa95bd | ||
|
ff331a45ee | ||
|
812ae47d80 | ||
|
6e58053e1b | ||
|
4fff5257e0 | ||
|
4e50537b39 | ||
|
582a830a80 | ||
|
d1693268f3 | ||
|
b24e4ba36c | ||
ccea3c1c53 | |||
a336838af8 | |||
6a0027fb64 | |||
494e1bd8d6 | |||
49ec8462f0 | |||
|
7307e39ff6 | ||
|
8f90fa9d86 | ||
|
726cd5c01a | ||
|
e2b7732a5e | ||
b8b7091ec2 | |||
e568b5aa82 | |||
80476532d7 | |||
5bd824dee5 | |||
f0fa8d5ca5 | |||
|
9d3f43975b | ||
989f80c41f | |||
965f2e3101 | |||
a20b1a9144 | |||
|
771d7f7b89 | ||
|
055d302022 | ||
|
d187a8423c | ||
7a506e07d4 | |||
497fd9a45d | |||
|
c0e64096a8 | ||
|
ba0f58b02d | ||
|
028543df53 | ||
|
dd9e7b71b2 |
63 changed files with 1242 additions and 184 deletions
|
@ -1,6 +1,6 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="test_incl_extensive_container_tests" type="JUnit" factoryName="JUnit">
|
<configuration default="false" name="test_incl_extensive_container_tests" type="JUnit" factoryName="JUnit">
|
||||||
<module name="provs.test" />
|
<module name="org.domaindrivenarchitecture.provs.provs.test" />
|
||||||
<option name="PACKAGE_NAME" value="org" />
|
<option name="PACKAGE_NAME" value="org" />
|
||||||
<option name="MAIN_CLASS_NAME" value="" />
|
<option name="MAIN_CLASS_NAME" value="" />
|
||||||
<option name="METHOD_NAME" value="" />
|
<option name="METHOD_NAME" value="" />
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
FROM ubuntu:latest
|
# image for usage in ci pipeline
|
||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
RUN apt-get update && apt-get -y install apt-utils sudo
|
RUN apt-get update && apt-get -y install apt-utils sudo
|
||||||
|
|
||||||
RUN useradd -m testuser && echo "testuser:testuserpw" | chpasswd && adduser testuser sudo
|
RUN useradd -m testuser && echo "testuser:testuserpw" | chpasswd && usermod -aG sudo testuser
|
||||||
RUN echo "testuser ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/testuser
|
RUN echo "testuser ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/testuser
|
||||||
|
|
||||||
USER testuser
|
USER testuser
|
||||||
|
|
47
README.md
47
README.md
|
@ -1,20 +1,20 @@
|
||||||
# provs
|
# provs
|
||||||
[![pipeline status](https://gitlab.com/domaindrivenarchitecture/provs/badges/master/pipeline.svg)](https://gitlab.com/domaindrivenarchitecture/provs/-/commits/master)
|
[![pipeline status](https://gitlab.com/domaindrivenarchitecture/provs/badges/master/pipeline.svg)](https://gitlab.com/domaindrivenarchitecture/provs/-/commits/master)
|
||||||
|
|
||||||
[<img src="https://domaindrivenarchitecture.org/img/delta-chat.svg" width=20 alt="DeltaChat"> chat over e-mail](mailto:buero@meissa-gmbh.de?subject=community-chat) | [<img src="https://meissa-gmbh.de/img/community/Mastodon_Logotype.svg" width=20 alt="team@social.meissa-gmbh.de"> team@social.meissa-gmbh.de](https://social.meissa-gmbh.de/@team) | [Website & Blog](https://domaindrivenarchitecture.org)
|
[<img src="https://domaindrivenarchitecture.org/img/delta-chat.svg" width=20 alt="DeltaChat"> chat over e-mail](mailto:buero@meissa-gmbh.de?subject=community-chat) | [<img src="https://meissa.de/images/parts/contact/mastodon36_hue9b2464f10b18e134322af482b9c915e_5501_filter_14705073121015236177.png" width=20 alt="M"> meissa@social.meissa-gmbh.de](https://social.meissa-gmbh.de/@meissa) | [Blog](https://domaindrivenarchitecture.org) | [Website](https://meissa.de)
|
||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
provs provides cli-based tools for
|
provs provides cli-based tools for
|
||||||
* provisioning a desktop (various kinds)
|
* provisioning desktop software for different desktop types:
|
||||||
|
* basic
|
||||||
|
* office
|
||||||
|
* IDE
|
||||||
* provisioning a k3s server
|
* provisioning a k3s server
|
||||||
* performing system checks
|
* performing system checks
|
||||||
|
|
||||||
Tasks can be run locally or remotely.
|
Tasks can be run locally or remotely.
|
||||||
|
|
||||||
## Status
|
|
||||||
|
|
||||||
under development - though we already set up a few IDEs and servers with provs.
|
|
||||||
|
|
||||||
## Try out
|
## Try out
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
@ -28,8 +28,9 @@ under development - though we already set up a few IDEs and servers with provs.
|
||||||
* Download the latest `provs-desktop.jar`,`provs-server.jar` and/or `provs-syspec.jar` from: https://gitlab.com/domaindrivenarchitecture/provs/-/releases
|
* Download the latest `provs-desktop.jar`,`provs-server.jar` and/or `provs-syspec.jar` from: https://gitlab.com/domaindrivenarchitecture/provs/-/releases
|
||||||
* Preferably into `/usr/local/bin` or any other folder where executables can be found by the system
|
* Preferably into `/usr/local/bin` or any other folder where executables can be found by the system
|
||||||
* Make the jar-file executable e.g. by `chmod +x provs-desktop.jar`
|
* Make the jar-file executable e.g. by `chmod +x provs-desktop.jar`
|
||||||
|
* Check with `provs-desktop.jar -h` to show help information
|
||||||
|
|
||||||
#### Build the binaries
|
###### Build the binaries
|
||||||
|
|
||||||
Instead of downloading the binaries you can build them yourself
|
Instead of downloading the binaries you can build them yourself
|
||||||
|
|
||||||
|
@ -106,6 +107,24 @@ To provision the grafana agent only to an existing k8s system, ensure that the c
|
||||||
provs-server.jar k3s myuser@myhost.com -o grafana
|
provs-server.jar k3s myuser@myhost.com -o grafana
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To add the hetzner csi driver and encrypted volumes to your k3s installation add the following to the config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
hetzner:
|
||||||
|
hcloudApiToken:
|
||||||
|
source: "PLAIN" # PLAIN, GOPASS or PROMPT
|
||||||
|
parameter: "mypassword" # the api key for the hetzner cloud
|
||||||
|
encryptionPassphrase:
|
||||||
|
source: "PLAIN" # PLAIN, GOPASS or PROMPT
|
||||||
|
parameter: "mypassword" # the encryption passphrase for created volumes
|
||||||
|
```
|
||||||
|
|
||||||
|
To provision the grafana agent only to an existing k8s system, ensure that the config (as above) is available and execute:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
provs-server.jar k3s myuser@myhost.com -o grafana
|
||||||
|
```
|
||||||
|
|
||||||
Reprovisioning the server can easily be done using the -r or --reprovision option.
|
Reprovisioning the server can easily be done using the -r or --reprovision option.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -144,7 +163,9 @@ Or to get help for subcommands e.g.
|
||||||
provs-desktop.jar ide -h
|
provs-desktop.jar ide -h
|
||||||
provs-server.jar k3s -h
|
provs-server.jar k3s -h
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development & mirrors
|
## Development & mirrors
|
||||||
|
|
||||||
Development happens at: https://repo.prod.meissa.de/meissa/provs
|
Development happens at: https://repo.prod.meissa.de/meissa/provs
|
||||||
|
|
||||||
Mirrors are:
|
Mirrors are:
|
||||||
|
@ -152,3 +173,17 @@ Mirrors are:
|
||||||
* https://github.com/DomainDrivenArchitecture/provs
|
* https://github.com/DomainDrivenArchitecture/provs
|
||||||
|
|
||||||
For more details about our repository model see: https://repo.prod.meissa.de/meissa/federate-your-repos
|
For more details about our repository model see: https://repo.prod.meissa.de/meissa/federate-your-repos
|
||||||
|
|
||||||
|
## Developer information
|
||||||
|
|
||||||
|
For using provs framework, add the required dependency to your project, then you can implement your own tasks e.g. by:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
|
|
||||||
|
fun Prov.myCustomTask() = task {
|
||||||
|
cmd("echo \"Hello world!\"")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See also [ForDevelopers.md](doc/ForDevelopers.md)
|
|
@ -1,5 +1,5 @@
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version_no = "1.7.20"
|
ext.kotlin_version_no = "1.8.20"
|
||||||
ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID
|
ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -16,7 +16,7 @@ plugins {
|
||||||
apply plugin: "maven-publish"
|
apply plugin: "maven-publish"
|
||||||
|
|
||||||
|
|
||||||
version = "0.33.0"
|
version = "0.36.1-SNAPSHOT"
|
||||||
group = "org.domaindrivenarchitecture.provs"
|
group = "org.domaindrivenarchitecture.provs"
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,8 +76,8 @@ dependencies {
|
||||||
api('com.charleskorn.kaml:kaml:0.54.0')
|
api('com.charleskorn.kaml:kaml:0.54.0')
|
||||||
|
|
||||||
api("org.slf4j:slf4j-api:1.7.36")
|
api("org.slf4j:slf4j-api:1.7.36")
|
||||||
api('ch.qos.logback:logback-classic:1.2.11')
|
api('ch.qos.logback:logback-classic:1.4.14')
|
||||||
api('ch.qos.logback:logback-core:1.2.11')
|
api('ch.qos.logback:logback-core:1.4.14')
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version_no")
|
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version_no")
|
||||||
implementation("com.hierynomus:sshj:0.32.0")
|
implementation("com.hierynomus:sshj:0.32.0")
|
||||||
|
|
7
build.py
7
build.py
|
@ -8,7 +8,7 @@ name = "provs"
|
||||||
PROJECT_ROOT_PATH = "."
|
PROJECT_ROOT_PATH = "."
|
||||||
|
|
||||||
|
|
||||||
version = "0.33.0"
|
version = "0.36.1-dev"
|
||||||
|
|
||||||
|
|
||||||
@init
|
@init
|
||||||
|
@ -131,6 +131,11 @@ def publish_release(project):
|
||||||
build.publish_artifacts()
|
build.publish_artifacts()
|
||||||
|
|
||||||
|
|
||||||
|
@task
|
||||||
|
def inst(project):
|
||||||
|
run("./gradlew inst", shell=True)
|
||||||
|
|
||||||
|
|
||||||
def increase_version_number(project, release_type):
|
def increase_version_number(project, release_type):
|
||||||
build = get_devops_build(project)
|
build = get_devops_build(project)
|
||||||
build.update_release_type(release_type)
|
build.update_release_type(release_type)
|
||||||
|
|
|
@ -1,22 +1,8 @@
|
||||||
# Information for developers
|
This page provides information for developers.
|
||||||
|
|
||||||
## Create a provs jar-file
|
# Tasks
|
||||||
|
|
||||||
* Clone this repo
|
## What is a task ?
|
||||||
* Build the jar-file by `./gradlew uberjarDesktop`
|
|
||||||
* In folder build/libs you'll find the file `provs-desktop.jar`
|
|
||||||
|
|
||||||
This uberjar is a Java jar-file including all required dependencies.
|
|
||||||
|
|
||||||
## Task
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
fun Prov.provisionK3s() = task { /* ... code and subtasks come here ... */ }
|
|
||||||
```
|
|
||||||
|
|
||||||
If you're having a deeper look into the provs code, you'll see regularly a task definition like this and might wonder ...
|
|
||||||
|
|
||||||
### What is a task ?
|
|
||||||
|
|
||||||
A task is the **basic execution unit** in provs. When executed, each task produces exactly one result (line) with either success or failure.
|
A task is the **basic execution unit** in provs. When executed, each task produces exactly one result (line) with either success or failure.
|
||||||
|
|
||||||
|
@ -26,9 +12,108 @@ The success or failure is computed automatically in the following way:
|
||||||
* a task defined with **optional** (i.e. `= optional { /* ... */ }` always returns success (even if there are failing subtasks)
|
* a task defined with **optional** (i.e. `= optional { /* ... */ }` always returns success (even if there are failing subtasks)
|
||||||
* **requireLast** defines a task which must provide an explicit result and solely this result counts for success calculation
|
* **requireLast** defines a task which must provide an explicit result and solely this result counts for success calculation
|
||||||
|
|
||||||
|
## Task declaration
|
||||||
|
|
||||||
|
### Recommended way
|
||||||
|
|
||||||
|
A task can be declared by
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
fun Prov.myCustomTask() = task { /* ... code and subtasks come here ... */ }
|
||||||
|
// e.g.
|
||||||
|
fun Prov.myEchoTask() = task {
|
||||||
|
cmd("echo hello world!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The task will succeed if all sub-tasks (called tasks during execution) have succeeded resp. if no sub-task was called.
|
||||||
|
|
||||||
|
### Alternative ways
|
||||||
|
|
||||||
|
The following ways are equivalent but are more verbose:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Redundant declaration of the return type (ProvResult), which is already declared by task
|
||||||
|
fun Prov.myCustomTask(): ProvResult = task { /* ... code and subtasks come here ... */ }
|
||||||
|
|
||||||
|
// Redundant parentheses behind task
|
||||||
|
fun Prov.myCustomTask() = task() { /* ... code and subtasks come here ... */ }
|
||||||
|
|
||||||
|
// Redundant definition of the task name, but could be used to output a different task name
|
||||||
|
fun Prov.myCustomTask() = task("myCustomTask") { /* ... code and subtasks come here ... */ }
|
||||||
|
|
||||||
|
// Functionally equal, but with additional curly brackets
|
||||||
|
fun Prov.myCustomTask() { task { /* ... code and subtasks come here ... */ } }
|
||||||
|
```
|
||||||
|
|
||||||
|
Btw, the following lines and WILL NOT work as expected.
|
||||||
|
Due to too much lamda nesting, the code within the task is NOT executed:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
fun Prov.myCustomTask() = { task { /* ... code and subtasks come here ... */ } }
|
||||||
|
fun Prov.myCustomTask() {{ task { /* ... code and subtasks come here ... */ } }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add custom results
|
||||||
|
|
||||||
|
If you want to add a result explicitly, you can use method `addResultToEval`.
|
||||||
|
This maxy be used e.g. to add explicitly an error line, like in:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
fun Prov.myCustomTask() = task {
|
||||||
|
/* some other code ... */
|
||||||
|
addResultToEval(ProvResult(false, err = "my error msg"))
|
||||||
|
/* some other code ... */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
or alternatively you can use `taskWithResult`.
|
||||||
|
|
||||||
|
#### TaskWithResult
|
||||||
|
|
||||||
|
In case you want to include the return value (of type `ProvResult`) of a task to be added to the evaluation,
|
||||||
|
you can use `taskWithResult` instead of `task` and return the value, e.g. like
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
fun Prov.myEchoTask() = taskWithResult {
|
||||||
|
cmd("echo hello world!")
|
||||||
|
// ...
|
||||||
|
ProvResult(false, "Error: ... error message ...") // will be the returned as return value and included in the evaluation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
IMPORTANT: the value you want to return must be placed at the end of the lambda code (as usual in functional programming)!
|
||||||
|
The following will NOT work as expected:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
fun Prov.myEchoTask() = taskWithResult {
|
||||||
|
ProvResult(false, "Error: ... error message ...") // will be ignored
|
||||||
|
// the result from the call below (i.e. from task "cmd") will be returned by myEchoTask,
|
||||||
|
// which is redundant as its result is already included in the evaluation anyway.
|
||||||
|
cmd("echo hello world!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Task output
|
||||||
|
|
||||||
|
If a task is run e.g. with `local().myEchoTask()`, it will produce output like
|
||||||
|
```
|
||||||
|
> Success -- myEchoTask
|
||||||
|
---> Success -- cmd [/bin/bash, -c, echo hello world!]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Call hierarchy
|
## Call hierarchy
|
||||||
|
|
||||||
In the following link you can find an example of a sequence diagram when provisioning a desktop:
|
In the following link you can find an example of a sequence diagram when provisioning a desktop:
|
||||||
|
|
||||||
[ProvisionDesktopSequence.md](ProvisionDesktopSequence.md)
|
[ProvisionDesktopSequence.md](ProvisionDesktopSequence.md)
|
||||||
|
|
||||||
|
|
||||||
|
## Create a provs jar-file
|
||||||
|
|
||||||
|
* Clone this repo
|
||||||
|
* Build the jar-file by `./gradlew uberjarDesktop`
|
||||||
|
* In folder build/libs you'll find the file `provs-desktop.jar`
|
||||||
|
|
||||||
|
This uberjar is a Java jar-file including all required dependencies.
|
||||||
|
|
20
doc/Modularization.md
Normal file
20
doc/Modularization.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Modules
|
||||||
|
|
||||||
|
## Modules and their possible relations
|
||||||
|
|
||||||
|
![modularization.png](resources/modularization.png)
|
||||||
|
|
||||||
|
|
||||||
|
#### Modules
|
||||||
|
|
||||||
|
A,B,C: Modules with both domain and infrastructure layer code - common type of modules
|
||||||
|
D: Module with only domain: can sometimes make sense if only domain logic and no infrastructure logic is required
|
||||||
|
E: Module with only infrastructure: usually utility modules that just provide a collection of infrastructure functionality
|
||||||
|
|
||||||
|
#### Interactions
|
||||||
|
|
||||||
|
1. Domain calls (a function in) the infrastructure of the same module - common practice within a module
|
||||||
|
1. Domain calls (a function in) the domain another module - common practice between modules
|
||||||
|
1. Infrastructure calls infrastructure of another module - usually not recommended
|
||||||
|
1. Domain calls infrastructure in another module - can make sense in some cases e.g. if module D just needs some low-level function of module D. However where possible calling domain of module C should be preferred
|
||||||
|
1. Domain calls infrastructure in another module, which only has infrastructure - common practice for calling utility modules, which don't have a domain.
|
|
@ -2,8 +2,6 @@ This repository holds the documentation of the provs framework.
|
||||||
|
|
||||||
# Design principles
|
# Design principles
|
||||||
|
|
||||||
For usage examples it is recommended to have a look at [provs-scripts](https://gitlab.com/domaindrivenarchitecture/provs-scripts) or [provs-ubuntu-extensions](https://gitlab.com/domaindrivenarchitecture/provs-ubuntu-extensions).
|
|
||||||
|
|
||||||
## "Implarative"
|
## "Implarative"
|
||||||
|
|
||||||
Configuration management tools are usually classified as either **imperative** or **declarative**.
|
Configuration management tools are usually classified as either **imperative** or **declarative**.
|
||||||
|
|
10
doc/dev/upgradingGradleWrapper.md
Normal file
10
doc/dev/upgradingGradleWrapper.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
### Howto update gradle wrapper
|
||||||
|
|
||||||
|
1. To *latest* version (be aware for deprecated parts in future versions):
|
||||||
|
```shell
|
||||||
|
./gradlew wrapper --gradle-version latest
|
||||||
|
```
|
||||||
|
2. To *specific version:
|
||||||
|
```shell
|
||||||
|
./gradlew wrapper --gradle-version 8.6
|
||||||
|
```
|
BIN
doc/resources/modularization.png
Normal file
BIN
doc/resources/modularization.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
|
@ -38,8 +38,8 @@ fun main(args: Array<String>) {
|
||||||
null
|
null
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
println(
|
println(
|
||||||
"Error: File\u001b[31m ${configFileName} \u001b[0m was not found.\n" +
|
"Error: File\u001b[31m $configFileName \u001b[0m was not found.\n" +
|
||||||
"Pls copy file \u001B[31m desktop-config-example.yaml \u001B[0m to file \u001B[31m ${configFileName} \u001B[0m " +
|
"Pls copy file \u001B[31m desktop-config-example.yaml \u001B[0m to file \u001B[31m $configFileName \u001B[0m " +
|
||||||
"and change the content according to your needs."
|
"and change the content according to your needs."
|
||||||
)
|
)
|
||||||
null
|
null
|
||||||
|
|
|
@ -104,7 +104,8 @@ fun Prov.provisionBasicDesktop(
|
||||||
aptInstall(CLIP_TOOLS)
|
aptInstall(CLIP_TOOLS)
|
||||||
aptPurge(
|
aptPurge(
|
||||||
"remove-power-management xfce4-power-manager " +
|
"remove-power-management xfce4-power-manager " +
|
||||||
"xfce4-power-manager-plugins xfce4-power-manager-data"
|
"xfce4-power-manager-plugins xfce4-power-manager-data" +
|
||||||
|
"upower libimobiledevice6 libplist3 libusbmuxd6 usbmuxd bluez-cups"
|
||||||
)
|
)
|
||||||
aptPurge("abiword gnumeric")
|
aptPurge("abiword gnumeric")
|
||||||
aptPurge("popularity-contest")
|
aptPurge("popularity-contest")
|
||||||
|
@ -134,10 +135,13 @@ fun Prov.provisionOfficeDesktop() {
|
||||||
installDeltaChat()
|
installDeltaChat()
|
||||||
aptInstall(OFFICE_SUITE)
|
aptInstall(OFFICE_SUITE)
|
||||||
installZimWiki()
|
installZimWiki()
|
||||||
installNextcloudClient()
|
// installNextcloudClient() might not install - might need fix and working test
|
||||||
aptInstall(COMPARE_TOOLS)
|
aptInstall(COMPARE_TOOLS)
|
||||||
|
|
||||||
// optional, as installation of these tools often fail and as they are not considered mandatory
|
// VSCode is also required in office VM (not only in IDE desktop) e.g. as editor
|
||||||
|
installVSCode("python", "clojure")
|
||||||
|
|
||||||
|
// optional, as installation of these tools often fail and as they are not mandatory
|
||||||
optional {
|
optional {
|
||||||
aptInstall(DRAWING_TOOLS)
|
aptInstall(DRAWING_TOOLS)
|
||||||
}
|
}
|
||||||
|
@ -156,8 +160,10 @@ fun Prov.provisionIdeDesktop() {
|
||||||
installShadowCljs()
|
installShadowCljs()
|
||||||
installDevOps()
|
installDevOps()
|
||||||
provisionPython()
|
provisionPython()
|
||||||
|
installHugoByDeb()
|
||||||
|
|
||||||
// IDEs
|
// IDEs
|
||||||
installVSC("python", "clojure")
|
|
||||||
installIntelliJ()
|
installIntelliJ()
|
||||||
|
|
||||||
|
installKubeconform()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,13 @@ fun Prov.installDevOps() = task {
|
||||||
installTerraform()
|
installTerraform()
|
||||||
installKubectlAndTools()
|
installKubectlAndTools()
|
||||||
installYq()
|
installYq()
|
||||||
|
installGraalVM()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun Prov.installYq(
|
fun Prov.installYq(
|
||||||
version: String = "4.13.2",
|
version: String = "4.13.2",
|
||||||
sha256sum: String = "d7c89543d1437bf80fee6237eadc608d1b121c21a7cbbe79057d5086d74f8d79"
|
sha256sum: String = "d7c89543d1437bf80fee6237eadc608d1b121c21a7cbbe79057d5086d74f8d79"
|
||||||
): ProvResult = task {
|
) = task {
|
||||||
val path = "/usr/bin/"
|
val path = "/usr/bin/"
|
||||||
val filename = "yq"
|
val filename = "yq"
|
||||||
if (!checkFile(path + filename)) {
|
if (!checkFile(path + filename)) {
|
||||||
|
@ -38,7 +38,7 @@ fun Prov.installYq(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.installKubectlAndTools(): ProvResult = task {
|
fun Prov.installKubectlAndTools() = task {
|
||||||
|
|
||||||
task("installKubectl") {
|
task("installKubectl") {
|
||||||
if (!checkFile(KUBE_CONFIG_CONTEXT_SCRIPT)) {
|
if (!checkFile(KUBE_CONFIG_CONTEXT_SCRIPT)) {
|
||||||
|
@ -49,10 +49,34 @@ fun Prov.installKubectlAndTools(): ProvResult = task {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task("installKubeconform") {
|
||||||
|
|
||||||
|
installKubeconform()
|
||||||
|
}
|
||||||
installDevopsScripts()
|
installDevopsScripts()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.installKubectl(): ProvResult = task {
|
fun Prov.installKubeconform() = task {
|
||||||
|
// check for latest stable release on: https://github.com/yannh/kubeconform/releases
|
||||||
|
val version = "0.6.4"
|
||||||
|
val installationPath = "/usr/local/bin/"
|
||||||
|
val tmpDir = "~/tmp"
|
||||||
|
val filename = "kubeconform-linux-amd64"
|
||||||
|
val packedFilename = "$filename.tar.gz"
|
||||||
|
|
||||||
|
if ( !chk("kubeconform -v") || "v$version" != cmd("kubeconform -v").out?.trim() ) {
|
||||||
|
downloadFromURL(
|
||||||
|
"https://github.com/yannh/kubeconform/releases/download/v$version/$packedFilename",
|
||||||
|
path = tmpDir,
|
||||||
|
sha256sum = "2b4ebeaa4d5ac4843cf8f7b7e66a8874252b6b71bc7cbfc4ef1cbf85acec7c07"
|
||||||
|
)
|
||||||
|
cmd("sudo tar -xzf $packedFilename -C $installationPath", tmpDir)
|
||||||
|
} else {
|
||||||
|
ProvResult(true, out = "Kubeconform $version already installed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Prov.installKubectl() = task {
|
||||||
|
|
||||||
// see https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/
|
// see https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/
|
||||||
val kubectlVersion = "1.27.4"
|
val kubectlVersion = "1.27.4"
|
||||||
|
@ -67,19 +91,19 @@ fun Prov.installKubectl(): ProvResult = task {
|
||||||
downloadFromURL(
|
downloadFromURL(
|
||||||
"https://dl.k8s.io/release/v$kubectlVersion/bin/linux/amd64/kubectl",
|
"https://dl.k8s.io/release/v$kubectlVersion/bin/linux/amd64/kubectl",
|
||||||
path = tmpDir,
|
path = tmpDir,
|
||||||
// from https://dl.k8s.io/v1.23.0/bin/linux/amd64/kubectl.sha256
|
// from https://dl.k8s.io/v1.27.4/bin/linux/amd64/kubectl.sha256
|
||||||
sha256sum = "2d0f5ba6faa787878b642c151ccb2c3390ce4c1e6c8e2b59568b3869ba407c4f"
|
sha256sum = "4685bfcf732260f72fce58379e812e091557ef1dfc1bc8084226c7891dd6028f"
|
||||||
)
|
)
|
||||||
cmd("sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl", dir = tmpDir)
|
cmd("sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl", dir = tmpDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.configureKubectlBashCompletion(): ProvResult = task {
|
fun Prov.configureKubectlBashCompletion() = task {
|
||||||
cmd("kubectl completion bash >> /etc/bash_completion.d/kubernetes", sudo = true)
|
cmd("kubectl completion bash >> /etc/bash_completion.d/kubernetes", sudo = true)
|
||||||
createDir(".bashrc.d")
|
createDir(".bashrc.d")
|
||||||
createFileFromResource(KUBE_CONFIG_CONTEXT_SCRIPT, "kubectl.sh", RESOURCE_PATH)
|
createFileFromResource(KUBE_CONFIG_CONTEXT_SCRIPT, "kubectl.sh", RESOURCE_PATH)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.installDevopsScripts() {
|
fun Prov.installDevopsScripts() = task {
|
||||||
|
|
||||||
task("install ssh helper") {
|
task("install ssh helper") {
|
||||||
createFileFromResource(
|
createFileFromResource(
|
||||||
|
@ -121,7 +145,7 @@ fun Prov.installDevopsScripts() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.installTerraform(): ProvResult = task {
|
fun Prov.installTerraform() = task {
|
||||||
val dir = "/usr/lib/tfenv/"
|
val dir = "/usr/lib/tfenv/"
|
||||||
|
|
||||||
if (!checkDir(dir)) {
|
if (!checkDir(dir)) {
|
||||||
|
@ -134,34 +158,3 @@ fun Prov.installTerraform(): ProvResult = task {
|
||||||
cmd("tfenv install latest:^1.4.6", sudo = true)
|
cmd("tfenv install latest:^1.4.6", sudo = true)
|
||||||
cmd("tfenv use latest:^1.4.6", sudo = true)
|
cmd("tfenv use latest:^1.4.6", sudo = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------- AWS credentials file -----------------------------------------------
|
|
||||||
fun Prov.installAwsCredentials(id: String = "REPLACE_WITH_YOUR_ID", key: String = "REPLACE_WITH_YOUR_KEY"): ProvResult =
|
|
||||||
task {
|
|
||||||
val dir = "~/.aws"
|
|
||||||
|
|
||||||
if (!checkDir(dir)) {
|
|
||||||
createDirs(dir)
|
|
||||||
createFile("~/.aws/config", awsConfig())
|
|
||||||
createFile("~/.aws/credentials", awsCredentials(id, key))
|
|
||||||
} else {
|
|
||||||
ProvResult(true, "aws credential folder already installed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun awsConfig(): String {
|
|
||||||
return """
|
|
||||||
[default]
|
|
||||||
region = eu-central-1
|
|
||||||
output = json
|
|
||||||
""".trimIndent()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun awsCredentials(id: String, key: String): String {
|
|
||||||
return """
|
|
||||||
[default]
|
|
||||||
aws_access_key_id = $id
|
|
||||||
aws_secret_access_key = $key
|
|
||||||
""".trimIndent()
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,9 +11,10 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.web.base.downloadFrom
|
||||||
|
|
||||||
|
|
||||||
fun Prov.installGopass(
|
fun Prov.installGopass(
|
||||||
version: String = "1.15.5",
|
version: String = "1.15.13", // NOTE: when adjusting, pls also adjust checksum below and version of gopass bridge json api
|
||||||
enforceVersion: Boolean = false,
|
enforceVersion: Boolean = false,
|
||||||
sha256sum: String = "23ec10015c2643f22cb305859eb36d671094d463d2eb1798cc675e7bb06f4b39"
|
// from https://github.com/gopasspw/gopass/releases/tag/v1.15.13
|
||||||
|
sha256sum: String = "409ed5617e64fa2c781d5e2807ba7fcd65bc383a4e110f410f90b590e51aec55"
|
||||||
) = taskWithResult {
|
) = taskWithResult {
|
||||||
|
|
||||||
if (isPackageInstalled("gopass") && !enforceVersion) {
|
if (isPackageInstalled("gopass") && !enforceVersion) {
|
||||||
|
|
|
@ -22,10 +22,10 @@ fun Prov.downloadGopassBridge() = task {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.installGopassJsonApi() = taskWithResult {
|
fun Prov.installGopassJsonApi() = taskWithResult {
|
||||||
// see https://github.com/gopasspw/gopass-jsonapi
|
// from https://github.com/gopasspw/gopass-jsonapi/releases/tag/v1.15.13
|
||||||
val sha256sum = "ec9976e39a468428ae2eb1e2e0b9ceccba7f60d66b8097e2425b0c07f4fed108"
|
val sha256sum = "3162ab558301645024325ce2e419c1d67900e1faf95dc1774a36f1ebfc76389f"
|
||||||
val gopassJsonApiVersion = "1.15.5"
|
val gopassJsonApiVersion = "1.15.13"
|
||||||
val requiredGopassVersion = "1.15.5"
|
val requiredGopassVersion = "1.15.13"
|
||||||
val filename = "gopass-jsonapi_${gopassJsonApiVersion}_linux_amd64.deb"
|
val filename = "gopass-jsonapi_${gopassJsonApiVersion}_linux_amd64.deb"
|
||||||
val downloadUrl = "-L https://github.com/gopasspw/gopass-jsonapi/releases/download/v$gopassJsonApiVersion/$filename"
|
val downloadUrl = "-L https://github.com/gopasspw/gopass-jsonapi/releases/download/v$gopassJsonApiVersion/$filename"
|
||||||
val downloadDir = "${userHome()}Downloads"
|
val downloadDir = "${userHome()}Downloads"
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createDirs
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.web.base.downloadFromURL
|
||||||
|
|
||||||
|
const val GRAAL_VM_VERSION = "21.0.2"
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.installGraalVM() = task {
|
||||||
|
val tmpDir = "~/tmp"
|
||||||
|
val filename = "graalvm-community-jdk-"
|
||||||
|
val additionalPartFilename = "_linux-x64_bin"
|
||||||
|
val packedFilename = "$filename$GRAAL_VM_VERSION$additionalPartFilename.tar.gz"
|
||||||
|
val extractedFilenameHunch = "graalvm-community-openjdk-"
|
||||||
|
val installationPath = "/usr/lib/jvm/"
|
||||||
|
|
||||||
|
if ( GRAAL_VM_VERSION != graalVMVersion() || !chk("ls -d $installationPath$extractedFilenameHunch$GRAAL_VM_VERSION*")) {
|
||||||
|
downloadFromURL(
|
||||||
|
"https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-$GRAAL_VM_VERSION/$packedFilename",
|
||||||
|
path = tmpDir,
|
||||||
|
sha256sum = "b048069aaa3a99b84f5b957b162cc181a32a4330cbc35402766363c5be76ae48"
|
||||||
|
)
|
||||||
|
createDirs(installationPath, sudo = true)
|
||||||
|
cmd("sudo tar -C $installationPath -xzf $packedFilename", tmpDir)
|
||||||
|
val graalInstPath = installationPath + (cmd("ls /usr/lib/jvm/|grep -e graalvm-community-openjdk-$GRAAL_VM_VERSION").out?.replace("\n", ""))
|
||||||
|
cmd("sudo ln -sf $graalInstPath/lib/svm/bin/native-image /usr/local/bin/native-image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Prov.graalVMVersion(): String {
|
||||||
|
return cmdNoEval("/usr/local/bin/native-image --version|awk 'NR==1 {print $2}'").out?.trim() ?: ""
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.userHome
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptPurge
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.web.base.downloadFromURL
|
||||||
|
|
||||||
|
fun Prov.installHugoByDeb() = task {
|
||||||
|
val sha256sum = "46692ac9b79d5bc01b0f847f6dcf651d8630476de63e598ef61a8da9461d45cd"
|
||||||
|
val requiredHugoVersion = "0.125.5"
|
||||||
|
val filename = "hugo_extended_0.125.5_linux-amd64.deb"
|
||||||
|
val downloadUrl = "-L https://github.com/gohugoio/hugo/releases/download/v$requiredHugoVersion/$filename"
|
||||||
|
val downloadDir = "${userHome()}Downloads"
|
||||||
|
val currentHugoVersion = cmdNoEval("hugo version").out ?: ""
|
||||||
|
|
||||||
|
if (needsHugoInstall(currentHugoVersion, requiredHugoVersion)) {
|
||||||
|
if (isHugoInstalled(currentHugoVersion)) {
|
||||||
|
if (currentHugoVersion.contains("snap")) {
|
||||||
|
cmd("snap remove hugo", sudo = true)
|
||||||
|
} else {
|
||||||
|
aptPurge("hugo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aptInstall("gnupg2")
|
||||||
|
downloadFromURL(downloadUrl, filename, downloadDir, sha256sum = sha256sum)
|
||||||
|
cmd("dpkg -i $downloadDir/$filename", sudo = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun needsHugoInstall(currentHugoVersion: String?, requiredHugoVersion: String) : Boolean {
|
||||||
|
if (currentHugoVersion == null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (!isHugoInstalled(currentHugoVersion)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (!isHugoExtended(currentHugoVersion)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (isLowerHugoVersion(requiredHugoVersion, currentHugoVersion)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isHugoInstalled(hugoVersion: String?) : Boolean {
|
||||||
|
if (hugoVersion == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return hugoVersion.contains("hugo")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isHugoExtended(hugoVersion: String) : Boolean {
|
||||||
|
return hugoVersion.contains("extended")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isLowerHugoVersion(requiredHugoVersion: String, currentHugoVersion: String ) : Boolean {
|
||||||
|
val reqVersionNo = getHugoVersionNo(requiredHugoVersion)
|
||||||
|
val currentVersionNo = getHugoVersionNo(currentHugoVersion)
|
||||||
|
return when {
|
||||||
|
compareVersions(currentVersionNo, reqVersionNo).contains("lower") -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun compareVersions(firstVersion : List<Int>, secondVersion: List<Int>) : String {
|
||||||
|
var result = ""
|
||||||
|
for (i in 0..2) {
|
||||||
|
when {
|
||||||
|
firstVersion[i] > secondVersion[i] -> result += " higher"
|
||||||
|
firstVersion[i] < secondVersion[i] -> result += " lower"
|
||||||
|
firstVersion[i] == secondVersion[i] -> result += " equal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getHugoVersionNo(hugoVersion: String) : List<Int> {
|
||||||
|
// hugo v0.126.1-3d40ab+extended linux/amd64 BuildDate=2024-05-15T10:42:34Z VendorInfo=snap:0.126.1
|
||||||
|
var result = hugoVersion.split(" ")[1]
|
||||||
|
result = result.split("-")[0].removePrefix("v")
|
||||||
|
return result.split(".").map { it.toInt() }
|
||||||
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ val OPENCONNECT = "openconnect network-manager-openconnect network-manager-openc
|
||||||
|
|
||||||
val VPNC = "vpnc network-manager-vpnc network-manager-vpnc-gnome vpnc-scripts"
|
val VPNC = "vpnc network-manager-vpnc network-manager-vpnc-gnome vpnc-scripts"
|
||||||
|
|
||||||
val JAVA = "openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk jarwrapper"
|
val JAVA = "openjdk-17-jdk jarwrapper"
|
||||||
|
|
||||||
val DRAWING_TOOLS = "inkscape dia openboard graphviz"
|
val DRAWING_TOOLS = "inkscape dia openboard graphviz"
|
||||||
|
|
||||||
|
|
|
@ -6,33 +6,33 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInsta
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.isPackageInstalled
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.isPackageInstalled
|
||||||
|
|
||||||
|
|
||||||
fun Prov.installVSC(vararg options: String) = task {
|
fun Prov.installVSCode(vararg options: String) = task {
|
||||||
val clojureExtensions = setOf("betterthantomorrow.calva", "DavidAnson.vscode-markdownlint")
|
val clojureExtensions = setOf("betterthantomorrow.calva", "DavidAnson.vscode-markdownlint")
|
||||||
val pythonExtensions = setOf("ms-python.python")
|
val pythonExtensions = setOf("ms-python.python")
|
||||||
|
|
||||||
prerequisitesVSCinstall()
|
installVSCodePrerequisites()
|
||||||
|
|
||||||
installVSCPackage()
|
installVSCPackage()
|
||||||
installVSCodiumPackage()
|
installVSCodiumPackage()
|
||||||
|
|
||||||
if (options.contains("clojure")) {
|
if (options.contains("clojure")) {
|
||||||
installExtensionsCode(clojureExtensions)
|
installVSCodeExtensions(clojureExtensions)
|
||||||
installExtensionsCodium(clojureExtensions)
|
installVSCodiumExtensions(clojureExtensions)
|
||||||
}
|
}
|
||||||
if (options.contains("python")) {
|
if (options.contains("python")) {
|
||||||
installExtensionsCode(pythonExtensions)
|
installVSCodeExtensions(pythonExtensions)
|
||||||
installExtensionsCodium(pythonExtensions)
|
installVSCodiumExtensions(pythonExtensions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun Prov.prerequisitesVSCinstall() = task {
|
private fun Prov.installVSCodePrerequisites() = task {
|
||||||
aptInstall("curl gpg unzip apt-transport-https")
|
aptInstall("curl gpg unzip apt-transport-https")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Suppress("unused") // only required for installation of vscode via apt
|
@Suppress("unused") // only required for installation of vscode via apt
|
||||||
private fun Prov.installVscWithApt() = task {
|
private fun Prov.installVSCodeWithApt() = task {
|
||||||
val packageName = "code"
|
val packageName = "code"
|
||||||
if (!isPackageInstalled(packageName)) {
|
if (!isPackageInstalled(packageName)) {
|
||||||
// see https://code.visualstudio.com/docs/setup/linux
|
// see https://code.visualstudio.com/docs/setup/linux
|
||||||
|
@ -62,7 +62,7 @@ private fun Prov.installVSCodiumPackage() = task {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun Prov.installExtensionsCode(extensions: Set<String>) = optional {
|
private fun Prov.installVSCodeExtensions(extensions: Set<String>) = optional {
|
||||||
var res = ProvResult(true)
|
var res = ProvResult(true)
|
||||||
for (ext in extensions) {
|
for (ext in extensions) {
|
||||||
res = cmd("code --install-extension $ext")
|
res = cmd("code --install-extension $ext")
|
||||||
|
@ -71,11 +71,11 @@ private fun Prov.installExtensionsCode(extensions: Set<String>) = optional {
|
||||||
// Settings can be found at $HOME/.config/Code/User/settings.json
|
// Settings can be found at $HOME/.config/Code/User/settings.json
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prov.installExtensionsCodium(extensions: Set<String>) = optional {
|
private fun Prov.installVSCodiumExtensions(extensions: Set<String>) = optional {
|
||||||
var res = ProvResult(true)
|
var res = ProvResult(true)
|
||||||
for (ext in extensions) {
|
for (ext in extensions) {
|
||||||
res = cmd("codium --install-extension $ext")
|
res = ProvResult(res.success && cmd("codium --install-extension $ext").success)
|
||||||
}
|
}
|
||||||
res
|
res
|
||||||
// Settings can be found at $HOME/.config/Code/User/settings.json
|
// Settings can be found at $HOME/.config/VSCodium/User/settings.json
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,8 +80,8 @@ open class Prov protected constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A task is the base execution unit in provs. In the results overview it is represented by one line resp. result (of either success or failure).
|
* A task is the fundamental execution unit. In the results overview it is represented by one line with a success or failure result.
|
||||||
* Returns success if no sub-tasks are called or if all subtasks finish with success.
|
* Returns success if all sub-tasks finished with success or if no sub-tasks are called at all.
|
||||||
*/
|
*/
|
||||||
fun task(name: String? = null, taskLambda: Prov.() -> Unit): ProvResult {
|
fun task(name: String? = null, taskLambda: Prov.() -> Unit): ProvResult {
|
||||||
printDeprecationWarningIfLevel0("task")
|
printDeprecationWarningIfLevel0("task")
|
||||||
|
@ -89,8 +89,10 @@ open class Prov protected constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as task but the provided lambda is explicitly required to provide a ProvResult to be returned.
|
* Same as task above but the lambda parameter must have a ProvResult as return type.
|
||||||
* The returned result is included in the evaluation.
|
* The returned ProvResult is included in the success resp. failure evaluation,
|
||||||
|
* i.e. if the returned ProvResult from the lambda fails, the returned ProvResult from
|
||||||
|
* taskWithResult also fails, else success depends on potentially called sub-tasks.
|
||||||
*/
|
*/
|
||||||
fun taskWithResult(name: String? = null, taskLambda: Prov.() -> ProvResult): ProvResult {
|
fun taskWithResult(name: String? = null, taskLambda: Prov.() -> ProvResult): ProvResult {
|
||||||
printDeprecationWarningIfLevel0("taskWithResult")
|
printDeprecationWarningIfLevel0("taskWithResult")
|
||||||
|
@ -98,27 +100,27 @@ open class Prov protected constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* defines a task, which returns the returned result, the results of sub-tasks are not considered
|
* defines a task, which returns the returned result from the lambda, the results of sub-tasks are not considered
|
||||||
*/
|
*/
|
||||||
fun requireLast(name: String? = null, a: Prov.() -> ProvResult): ProvResult {
|
fun requireLast(name: String? = null, taskLambda: Prov.() -> ProvResult): ProvResult {
|
||||||
printDeprecationWarningIfLevel0("requireLast")
|
printDeprecationWarningIfLevel0("requireLast")
|
||||||
return evaluate(ResultMode.LAST, name) { a() }
|
return evaluate(ResultMode.LAST, name) { taskLambda() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* defines a task, which always returns success
|
* Defines a task, which always returns success.
|
||||||
*/
|
*/
|
||||||
fun optional(name: String? = null, a: Prov.() -> ProvResult): ProvResult {
|
fun optional(name: String? = null, taskLambda: Prov.() -> ProvResult): ProvResult {
|
||||||
printDeprecationWarningIfLevel0("optional")
|
printDeprecationWarningIfLevel0("optional")
|
||||||
return evaluate(ResultMode.OPTIONAL, name) { a() }
|
return evaluate(ResultMode.OPTIONAL, name) { taskLambda() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* defines a task, which exits the overall execution on failure
|
* Defines a task, which exits the overall execution on failure result of the taskLambda.
|
||||||
*/
|
*/
|
||||||
fun exitOnFailure(a: Prov.() -> ProvResult): ProvResult {
|
fun exitOnFailure(taskLambda: Prov.() -> ProvResult): ProvResult {
|
||||||
printDeprecationWarningIfLevel0("exitOnFailure")
|
printDeprecationWarningIfLevel0("exitOnFailure")
|
||||||
return evaluate(ResultMode.FAILEXIT) { a() }
|
return evaluate(ResultMode.FAILEXIT) { taskLambda() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -8,6 +8,8 @@ data class ProvResult(val success: Boolean,
|
||||||
val exception: Exception? = null,
|
val exception: Exception? = null,
|
||||||
val exit: String? = null) {
|
val exit: String? = null) {
|
||||||
|
|
||||||
|
val outTrimmed: String? = out?.trim()
|
||||||
|
|
||||||
constructor(returnCode : Int) : this(returnCode == 0)
|
constructor(returnCode : Int) : this(returnCode == 0)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import org.domaindrivenarchitecture.provs.framework.core.docker.dockerimages.Doc
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.docker.platforms.*
|
import org.domaindrivenarchitecture.provs.framework.core.docker.platforms.*
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.platforms.UbuntuProv
|
import org.domaindrivenarchitecture.provs.framework.core.platforms.UbuntuProv
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.processors.ContainerStartMode
|
import org.domaindrivenarchitecture.provs.framework.core.processors.ContainerStartMode
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.docker.platforms.*
|
|
||||||
|
|
||||||
private const val DOCKER_NOT_SUPPORTED = "docker not yet supported for "
|
private const val DOCKER_NOT_SUPPORTED = "docker not yet supported for "
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ fun Prov.dockerProvideImage(image: DockerImage, skipIfExisting: Boolean = true,
|
||||||
if (this is UbuntuProv) {
|
if (this is UbuntuProv) {
|
||||||
return this.dockerProvideImagePlatform(image, skipIfExisting, sudo)
|
return this.dockerProvideImagePlatform(image, skipIfExisting, sudo)
|
||||||
} else {
|
} else {
|
||||||
throw RuntimeException(DOCKER_NOT_SUPPORTED + (this as UbuntuProv).javaClass)
|
throw RuntimeException(DOCKER_NOT_SUPPORTED + this.javaClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ fun Prov.dockerImageExists(imageName: String, sudo: Boolean = true) : Boolean {
|
||||||
if (this is UbuntuProv) {
|
if (this is UbuntuProv) {
|
||||||
return this.dockerImageExistsPlatform(imageName, sudo)
|
return this.dockerImageExistsPlatform(imageName, sudo)
|
||||||
} else {
|
} else {
|
||||||
throw RuntimeException(DOCKER_NOT_SUPPORTED + (this as UbuntuProv).javaClass)
|
throw RuntimeException(DOCKER_NOT_SUPPORTED + this.javaClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ fun Prov.provideContainer(
|
||||||
if (this is UbuntuProv) {
|
if (this is UbuntuProv) {
|
||||||
return this.provideContainerPlatform(containerName, imageName, startMode, sudo, options, command)
|
return this.provideContainerPlatform(containerName, imageName, startMode, sudo, options, command)
|
||||||
} else {
|
} else {
|
||||||
throw RuntimeException(DOCKER_NOT_SUPPORTED + (this as UbuntuProv).javaClass)
|
throw RuntimeException(DOCKER_NOT_SUPPORTED + this.javaClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ fun Prov.containerRuns(containerName: String, sudo: Boolean = true) : Boolean {
|
||||||
if (this is UbuntuProv) {
|
if (this is UbuntuProv) {
|
||||||
return this.containerRunsPlatform(containerName, sudo)
|
return this.containerRunsPlatform(containerName, sudo)
|
||||||
} else {
|
} else {
|
||||||
throw RuntimeException(DOCKER_NOT_SUPPORTED + (this as UbuntuProv).javaClass)
|
throw RuntimeException(DOCKER_NOT_SUPPORTED + this.javaClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ fun Prov.runContainer(
|
||||||
if (this is UbuntuProv) {
|
if (this is UbuntuProv) {
|
||||||
return this.runContainerPlatform(containerName, imageName, sudo)
|
return this.runContainerPlatform(containerName, imageName, sudo)
|
||||||
} else {
|
} else {
|
||||||
throw RuntimeException(DOCKER_NOT_SUPPORTED + (this as UbuntuProv).javaClass)
|
throw RuntimeException(DOCKER_NOT_SUPPORTED + this.javaClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,16 +84,17 @@ fun Prov.exitAndRmContainer(
|
||||||
if (this is UbuntuProv) {
|
if (this is UbuntuProv) {
|
||||||
return this.exitAndRmContainerPlatform(containerName, sudo)
|
return this.exitAndRmContainerPlatform(containerName, sudo)
|
||||||
} else {
|
} else {
|
||||||
throw RuntimeException(DOCKER_NOT_SUPPORTED + (this as UbuntuProv).javaClass)
|
throw RuntimeException(DOCKER_NOT_SUPPORTED + this.javaClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
fun Prov.containerExec(containerName: String, cmd: String, sudo: Boolean = true): ProvResult {
|
fun Prov.containerExec(containerName: String, cmd: String, sudo: Boolean = true): ProvResult {
|
||||||
if (this is UbuntuProv) {
|
if (this is UbuntuProv) {
|
||||||
return this.containerExecPlatform(containerName, cmd, sudo)
|
return this.containerExecPlatform(containerName, cmd, sudo)
|
||||||
} else {
|
} else {
|
||||||
throw RuntimeException(DOCKER_NOT_SUPPORTED + (this as UbuntuProv).javaClass)
|
throw RuntimeException(DOCKER_NOT_SUPPORTED + this.javaClass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,12 @@ class UbuntuPlusUser(private val userName: String = "testuser") : DockerImage {
|
||||||
|
|
||||||
override fun imageText(): String {
|
override fun imageText(): String {
|
||||||
return """
|
return """
|
||||||
FROM ubuntu:20.04
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
RUN apt-get update && apt-get -y install sudo
|
RUN apt-get update && apt-get -y install sudo
|
||||||
RUN useradd -m $userName && echo "$userName:$userName" | chpasswd && adduser $userName sudo
|
RUN useradd -m $userName && echo "$userName:$userName" | chpasswd && usermod -aG sudo $userName
|
||||||
RUN echo "$userName ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/$userName
|
RUN echo "$userName ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/$userName
|
||||||
|
|
||||||
USER $userName
|
USER $userName
|
||||||
|
|
|
@ -7,10 +7,10 @@ import org.domaindrivenarchitecture.provs.framework.core.processors.Processor
|
||||||
const val SHELL = "/bin/bash"
|
const val SHELL = "/bin/bash"
|
||||||
|
|
||||||
|
|
||||||
class UbuntuProv internal constructor(
|
open class UbuntuProv(
|
||||||
processor: Processor = LocalProcessor(),
|
processor: Processor = LocalProcessor(),
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
progressType: ProgressType
|
progressType: ProgressType = ProgressType.BASIC
|
||||||
) : Prov(processor, name, progressType) {
|
) : Prov(processor, name, progressType) {
|
||||||
|
|
||||||
override fun cmd(cmd: String, dir: String?, sudo: Boolean): ProvResult = taskWithResult {
|
override fun cmd(cmd: String, dir: String?, sudo: Boolean): ProvResult = taskWithResult {
|
||||||
|
@ -30,14 +30,16 @@ class UbuntuProv internal constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildCommand(vararg args: String): String {
|
private fun buildCommand(vararg args: String): String {
|
||||||
return if (args.size == 1)
|
return if (args.size == 1) {
|
||||||
args[0].escapeAndEncloseByDoubleQuoteForShell()
|
args[0].escapeAndEncloseByDoubleQuoteForShell()
|
||||||
else
|
} else {
|
||||||
if (args.size == 3 && SHELL.equals(args[0]) && "-c".equals(args[1]))
|
if (args.size == 3 && SHELL == args[0] && "-c" == args[1]) {
|
||||||
SHELL + " -c " + args[2].escapeAndEncloseByDoubleQuoteForShell()
|
SHELL + " -c " + args[2].escapeAndEncloseByDoubleQuoteForShell()
|
||||||
else
|
} else {
|
||||||
args.joinToString(separator = " ")
|
args.joinToString(separator = " ")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun commandWithDirAndSudo(cmd: String, dir: String?, sudo: Boolean): String {
|
private fun commandWithDirAndSudo(cmd: String, dir: String?, sudo: Boolean): String {
|
||||||
|
|
|
@ -5,13 +5,14 @@ import org.slf4j.LoggerFactory
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
|
||||||
private fun getOsName(): String {
|
private fun getOsName(): String {
|
||||||
return System.getProperty("os.name")
|
return System.getProperty("os.name")
|
||||||
}
|
}
|
||||||
|
|
||||||
open class LocalProcessor : Processor {
|
open class LocalProcessor(val useHomeDirAsWorkingDir: Boolean = true) : Processor {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@Suppress("JAVA_CLASS_ON_COMPANION")
|
@Suppress("JAVA_CLASS_ON_COMPANION")
|
||||||
|
@ -26,7 +27,12 @@ open class LocalProcessor : Processor {
|
||||||
|
|
||||||
private fun workingDir() : String
|
private fun workingDir() : String
|
||||||
{
|
{
|
||||||
return System.getProperty("user.home") ?: File.separator
|
return if (useHomeDirAsWorkingDir) {
|
||||||
|
System.getProperty("user.home") ?: File.separator
|
||||||
|
} else {
|
||||||
|
// folder in which program was started
|
||||||
|
Paths.get("").toAbsolutePath().toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun exec(vararg args: String): ProcessResult {
|
override fun exec(vararg args: String): ProcessResult {
|
||||||
|
|
|
@ -93,9 +93,9 @@ class RemoteProcessor(val host: InetAddress, val user: String, val password: Sec
|
||||||
var session: Session? = null
|
var session: Session? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session = ssh.startSession()
|
session = ssh.startSession() ?: throw IllegalStateException("ERROR: Could not start ssh session.")
|
||||||
|
|
||||||
val cmd: Command = session!!.exec(cmdString)
|
val cmd: Command = session.exec(cmdString)
|
||||||
val out = BufferedReader(InputStreamReader(cmd.inputStream)).use { it.readText() }
|
val out = BufferedReader(InputStreamReader(cmd.inputStream)).use { it.readText() }
|
||||||
val err = BufferedReader(InputStreamReader(cmd.errorStream)).use { it.readText() }
|
val err = BufferedReader(InputStreamReader(cmd.errorStream)).use { it.readText() }
|
||||||
cmd.join(100, TimeUnit.SECONDS)
|
cmd.join(100, TimeUnit.SECONDS)
|
||||||
|
|
|
@ -201,7 +201,7 @@ fun Prov.fileContentLargeFile(file: String, sudo: Boolean = false, chunkSize: In
|
||||||
// check first chunk
|
// check first chunk
|
||||||
if (resultString == null) {
|
if (resultString == null) {
|
||||||
if (!chunkResult.success) {
|
if (!chunkResult.success) {
|
||||||
return resultString
|
return null
|
||||||
} else {
|
} else {
|
||||||
resultString = ""
|
resultString = ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ abstract class SecretSource(protected val input: String) {
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
enum class SecretSourceType() {
|
enum class SecretSourceType {
|
||||||
|
|
||||||
PLAIN, FILE, PROMPT, PASS, GOPASS;
|
PLAIN, FILE, PROMPT, PASS, GOPASS;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.server.domain.hetzner_csi
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.server.infrastructure.provisionHetznerCSIForK8s
|
||||||
|
|
||||||
|
fun Prov.provisionHetznerCSI(configResolved: HetznerCSIConfigResolved) =
|
||||||
|
provisionHetznerCSIForK8s(configResolved.hcloudApiToken, configResolved.encryptionPassphrase)
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.server.domain.hetzner_csi
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSupplier
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HetznerCSIConfig (
|
||||||
|
val hcloudApiToken: SecretSupplier,
|
||||||
|
val encryptionPassphrase: SecretSupplier,
|
||||||
|
) {
|
||||||
|
fun resolveSecret(): HetznerCSIConfigResolved = HetznerCSIConfigResolved(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class HetznerCSIConfigResolved(val configUnresolved: HetznerCSIConfig) {
|
||||||
|
val hcloudApiToken: Secret = configUnresolved.hcloudApiToken.secret()
|
||||||
|
val encryptionPassphrase: Secret = configUnresolved.encryptionPassphrase.secret()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HetznerCSIConfigHolder(
|
||||||
|
val hetzner: HetznerCSIConfig
|
||||||
|
)
|
|
@ -2,6 +2,8 @@ package org.domaindrivenarchitecture.provs.server.domain.k3s
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.configuration.infrastructure.DefaultConfigFileRepository
|
import org.domaindrivenarchitecture.provs.configuration.infrastructure.DefaultConfigFileRepository
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.HetznerCSIConfigResolved
|
||||||
|
import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.provisionHetznerCSI
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k8s_grafana_agent.GrafanaAgentConfigResolved
|
import org.domaindrivenarchitecture.provs.server.domain.k8s_grafana_agent.GrafanaAgentConfigResolved
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k8s_grafana_agent.provisionGrafanaAgent
|
import org.domaindrivenarchitecture.provs.server.domain.k8s_grafana_agent.provisionGrafanaAgent
|
||||||
import org.domaindrivenarchitecture.provs.server.infrastructure.*
|
import org.domaindrivenarchitecture.provs.server.infrastructure.*
|
||||||
|
@ -11,6 +13,7 @@ import kotlin.system.exitProcess
|
||||||
fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task {
|
fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task {
|
||||||
|
|
||||||
val grafanaConfigResolved: GrafanaAgentConfigResolved? = findK8sGrafanaConfig(cli.configFileName)?.resolveSecret()
|
val grafanaConfigResolved: GrafanaAgentConfigResolved? = findK8sGrafanaConfig(cli.configFileName)?.resolveSecret()
|
||||||
|
val hcloudConfigResolved: HetznerCSIConfigResolved? = findHetznerCSIConfig(cli.configFileName)?.resolveSecret()
|
||||||
|
|
||||||
if (cli.onlyModules == null) {
|
if (cli.onlyModules == null) {
|
||||||
val k3sConfig: K3sConfig = getK3sConfig(cli.configFileName)
|
val k3sConfig: K3sConfig = getK3sConfig(cli.configFileName)
|
||||||
|
@ -18,9 +21,10 @@ fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task {
|
||||||
val k3sConfigReprovision = k3sConfig.copy(reprovision = cli.reprovision || k3sConfig.reprovision)
|
val k3sConfigReprovision = k3sConfig.copy(reprovision = cli.reprovision || k3sConfig.reprovision)
|
||||||
|
|
||||||
val applicationFile = cli.applicationFileName?.let { DefaultApplicationFileRepository(cli.applicationFileName).getFile() }
|
val applicationFile = cli.applicationFileName?.let { DefaultApplicationFileRepository(cli.applicationFileName).getFile() }
|
||||||
provisionK3s(k3sConfigReprovision, grafanaConfigResolved, applicationFile)
|
provisionK3s(k3sConfigReprovision, grafanaConfigResolved, hcloudConfigResolved, applicationFile)
|
||||||
} else {
|
} else {
|
||||||
provisionGrafana(cli.onlyModules, grafanaConfigResolved)
|
provisionGrafana(cli.onlyModules, grafanaConfigResolved)
|
||||||
|
provisionHetznerCSI(cli.onlyModules, hcloudConfigResolved)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +34,7 @@ fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task {
|
||||||
fun Prov.provisionK3s(
|
fun Prov.provisionK3s(
|
||||||
k3sConfig: K3sConfig,
|
k3sConfig: K3sConfig,
|
||||||
grafanaConfigResolved: GrafanaAgentConfigResolved? = null,
|
grafanaConfigResolved: GrafanaAgentConfigResolved? = null,
|
||||||
|
hetznerCSIConfigResolved: HetznerCSIConfigResolved? = null,
|
||||||
applicationFile: ApplicationFile? = null
|
applicationFile: ApplicationFile? = null
|
||||||
) = task {
|
) = task {
|
||||||
|
|
||||||
|
@ -53,6 +58,10 @@ fun Prov.provisionK3s(
|
||||||
provisionGrafanaAgent(grafanaConfigResolved)
|
provisionGrafanaAgent(grafanaConfigResolved)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hetznerCSIConfigResolved != null) {
|
||||||
|
provisionHetznerCSI(hetznerCSIConfigResolved)
|
||||||
|
}
|
||||||
|
|
||||||
if (applicationFile != null) {
|
if (applicationFile != null) {
|
||||||
provisionK3sApplication(applicationFile)
|
provisionK3sApplication(applicationFile)
|
||||||
}
|
}
|
||||||
|
@ -60,6 +69,8 @@ fun Prov.provisionK3s(
|
||||||
if (!k3sConfig.reprovision) {
|
if (!k3sConfig.reprovision) {
|
||||||
provisionServerCliConvenience()
|
provisionServerCliConvenience()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
installK9s()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prov.provisionGrafana(
|
private fun Prov.provisionGrafana(
|
||||||
|
@ -75,3 +86,18 @@ private fun Prov.provisionGrafana(
|
||||||
provisionGrafanaAgent(grafanaConfigResolved)
|
provisionGrafanaAgent(grafanaConfigResolved)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Prov.provisionHetznerCSI(
|
||||||
|
onlyModules: List<String>?,
|
||||||
|
hetznerCSIConfigResolved: HetznerCSIConfigResolved?
|
||||||
|
) = task {
|
||||||
|
|
||||||
|
if (onlyModules != null && onlyModules.contains(ServerOnlyModule.HETZNER_CSI.name.lowercase())) {
|
||||||
|
if (hetznerCSIConfigResolved == null) {
|
||||||
|
println("ERROR: Could not find grafana config.")
|
||||||
|
exitProcess(7)
|
||||||
|
}
|
||||||
|
provisionHetznerCSI(hetznerCSIConfigResolved)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.domaindrivenarchitecture.provs.server.domain.k3s
|
package org.domaindrivenarchitecture.provs.server.domain.k3s
|
||||||
|
|
||||||
enum class ServerOnlyModule {
|
enum class ServerOnlyModule {
|
||||||
GRAFANA
|
GRAFANA,
|
||||||
|
HETZNER_CSI
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package org.domaindrivenarchitecture.provs.server.infrastructure
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.ProvResult
|
import org.domaindrivenarchitecture.provs.framework.core.ProvResult
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFile
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFileFromResource
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFileFromResource
|
||||||
|
|
||||||
private const val resourcePath = "org/domaindrivenarchitecture/provs/desktop/infrastructure"
|
private const val resourcePath = "org/domaindrivenarchitecture/provs/desktop/infrastructure"
|
||||||
|
@ -16,7 +15,8 @@ fun Prov.provisionServerCliConvenience() = task {
|
||||||
fun Prov.provisionKubectlCompletionAndAlias(): ProvResult = task {
|
fun Prov.provisionKubectlCompletionAndAlias(): ProvResult = task {
|
||||||
cmd("kubectl completion bash | sudo tee /etc/bash_completion.d/kubectl > /dev/null")
|
cmd("kubectl completion bash | sudo tee /etc/bash_completion.d/kubectl > /dev/null")
|
||||||
cmd("echo 'alias k=kubectl' >> ~/.bashrc")
|
cmd("echo 'alias k=kubectl' >> ~/.bashrc")
|
||||||
cmd("echo 'complete -o default -F __start_kubectl k' >>~/.bashrc")
|
cmd("echo 'alias k9=\"k9s --kubeconfig /etc/kubernetes/admin.conf\"' >> ~/.bashrc")
|
||||||
|
cmd("echo 'complete -o default -F __start_kubectl k' >> ~/.bashrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.provisionVimrc(): ProvResult = task {
|
fun Prov.provisionVimrc(): ProvResult = task {
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.server.infrastructure
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFileFromResource
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFileFromResourceTemplate
|
||||||
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.FileMode
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
private const val hetznerCSIResourceDir = "org/domaindrivenarchitecture/provs/server/infrastructure/hetznerCSI/"
|
||||||
|
fun Prov.provisionHetznerCSIForK8s(hetznerApiToken: Secret, encryptionPassphrase: Secret) {
|
||||||
|
// CSI Driver
|
||||||
|
createFileFromResourceTemplate(
|
||||||
|
k3sManualManifestsDir + "hcloud-api-token-secret.yaml",
|
||||||
|
"hcloud-api-token-secret.template.yaml",
|
||||||
|
resourcePath = hetznerCSIResourceDir,
|
||||||
|
posixFilePermission = "644",
|
||||||
|
values = mapOf(
|
||||||
|
"HETZNER_API_TOKEN" to hetznerApiToken.plain()
|
||||||
|
))
|
||||||
|
cmd("kubectl apply -f hcloud-api-token-secret.yaml", k3sManualManifestsDir)
|
||||||
|
applyHetznerCSIFileFromResource(File(k3sManualManifestsDir, "hcloud-csi.yaml"))
|
||||||
|
|
||||||
|
// Encryption
|
||||||
|
createFileFromResourceTemplate(
|
||||||
|
k3sManualManifestsDir + "hcloud-encryption-secret.yaml",
|
||||||
|
"hcloud-encryption-secret.template.yaml",
|
||||||
|
resourcePath = hetznerCSIResourceDir,
|
||||||
|
posixFilePermission = "644",
|
||||||
|
values = mapOf(
|
||||||
|
"HETZNER_ENCRYPTION_PASSPHRASE" to encryptionPassphrase.plain()
|
||||||
|
))
|
||||||
|
cmd("kubectl apply -f hcloud-encryption-secret.yaml", k3sManualManifestsDir)
|
||||||
|
applyHetznerCSIFileFromResource(File(k3sManualManifestsDir, "hcloud-encrypted-storage-class.yaml"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Prov.createHetznerCSIFileFromResource(
|
||||||
|
file: File,
|
||||||
|
posixFilePermission: FileMode? = "644"
|
||||||
|
) = task {
|
||||||
|
createFileFromResource(
|
||||||
|
file.path,
|
||||||
|
file.name,
|
||||||
|
hetznerCSIResourceDir,
|
||||||
|
posixFilePermission,
|
||||||
|
sudo = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Prov.applyHetznerCSIFileFromResource(file: File, posixFilePermission: FileMode? = "644") = task {
|
||||||
|
createHetznerCSIFileFromResource(file, posixFilePermission)
|
||||||
|
cmd("kubectl apply -f ${file.path}", sudo = true)
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.server.infrastructure
|
||||||
|
|
||||||
|
import com.charleskorn.kaml.MissingRequiredPropertyException
|
||||||
|
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.readFromFile
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.toYaml
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.yamlToType
|
||||||
|
import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.HetznerCSIConfig
|
||||||
|
import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.HetznerCSIConfigHolder
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileWriter
|
||||||
|
|
||||||
|
private const val DEFAULT_CONFIG_FILE = "server-config.yaml"
|
||||||
|
|
||||||
|
fun findHetznerCSIConfig(fileName: ConfigFileName? = null): HetznerCSIConfig? {
|
||||||
|
val filePath = fileName?.fileName ?: DEFAULT_CONFIG_FILE
|
||||||
|
|
||||||
|
return if(File(filePath).exists()) {
|
||||||
|
try {
|
||||||
|
readFromFile(filePath).yamlToType<HetznerCSIConfigHolder>().hetzner
|
||||||
|
} catch (e: MissingRequiredPropertyException) {
|
||||||
|
if (e.message.contains("Property 'hetzner'")) null else throw e
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
internal fun writeConfig(config: HetznerCSIConfigHolder, fileName: String = "hetzner-config.yaml") =
|
||||||
|
FileWriter(fileName).use { it.write(config.toYaml()) }
|
|
@ -39,7 +39,6 @@ private val selfSignedCertificate = File(k3sManualManifestsDir, "selfsigned-cert
|
||||||
|
|
||||||
private val localPathProvisionerConfig = File(k3sManualManifestsDir, "local-path-provisioner-config.yaml")
|
private val localPathProvisionerConfig = File(k3sManualManifestsDir, "local-path-provisioner-config.yaml")
|
||||||
|
|
||||||
|
|
||||||
// ----------------------------------- public functions --------------------------------
|
// ----------------------------------- public functions --------------------------------
|
||||||
|
|
||||||
fun Prov.testConfigExists(): Boolean {
|
fun Prov.testConfigExists(): Boolean {
|
||||||
|
@ -124,6 +123,7 @@ fun Prov.installK3s(k3sConfig: K3sConfig): ProvResult {
|
||||||
applyK3sFileFromResource(k3sMiddleWareHttpsRedirect)
|
applyK3sFileFromResource(k3sMiddleWareHttpsRedirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// other
|
||||||
applyK3sFileFromResource(localPathProvisionerConfig)
|
applyK3sFileFromResource(localPathProvisionerConfig)
|
||||||
|
|
||||||
cmd("kubectl set env deployment -n kube-system local-path-provisioner DEPLOY_DATE=\"$(date)\"", sudo = true)
|
cmd("kubectl set env deployment -n kube-system local-path-provisioner DEPLOY_DATE=\"$(date)\"", sudo = true)
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.server.infrastructure
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.web.base.downloadFromURL
|
||||||
|
|
||||||
|
|
||||||
|
const val K9S_VERSION = "v0.32.5"
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.installK9s() = task {
|
||||||
|
if (cmdNoEval("k9s version").out?.contains(K9S_VERSION) != true) {
|
||||||
|
downloadFromURL("https://github.com/derailed/k9s/releases/download/$K9S_VERSION/k9s_linux_amd64.deb", "k9s_linux_amd64.deb", "/tmp")
|
||||||
|
cmd("sudo dpkg -i k9s_linux_amd64.deb", "/tmp")
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ function usage() {
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
local cluster_name="${1}";
|
local cluster_name="${1}";
|
||||||
local domain_name="${2:-meissa-gmbh.de}";
|
local domain_name="${2:-meissa.de}";
|
||||||
|
|
||||||
/usr/local/bin/k3s-create-context.sh ${cluster_name} ${domain_name}
|
/usr/local/bin/k3s-create-context.sh ${cluster_name} ${domain_name}
|
||||||
kubectl config use-context ${cluster_name}
|
kubectl config use-context ${cluster_name}
|
||||||
|
|
|
@ -4,8 +4,9 @@ set -o noglob
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
local cluster_name="${1}"; shift
|
local cluster_name="${1}"; shift
|
||||||
|
local domain_name="${1:-meissa.de}"; shift
|
||||||
|
|
||||||
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@${cluster_name}.meissa-gmbh.de -L 8002:localhost:8002 -L 6443:192.168.5.1:6443
|
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@${cluster_name}.${domain_name} -L 8002:localhost:8002 -L 6443:192.168.5.1:6443
|
||||||
}
|
}
|
||||||
|
|
||||||
main $1
|
main $1
|
||||||
|
|
|
@ -4,8 +4,9 @@ set -o noglob
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
local cluster_name="${1}"; shift
|
local cluster_name="${1}"; shift
|
||||||
|
local domain_name="${1:-meissa.de}"; shift
|
||||||
|
|
||||||
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@${cluster_name}.meissa-gmbh.de
|
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@${cluster_name}.${domain_name}
|
||||||
}
|
}
|
||||||
|
|
||||||
main $1
|
main $1
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: hcloud
|
||||||
|
namespace: kube-system
|
||||||
|
stringData:
|
||||||
|
token: $HETZNER_API_TOKEN
|
|
@ -0,0 +1,401 @@
|
||||||
|
# Version 2.6.0
|
||||||
|
# Source: hcloud-csi/templates/controller/serviceaccount.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: hcloud-csi-controller
|
||||||
|
namespace: "kube-system"
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: hcloud-csi
|
||||||
|
app.kubernetes.io/instance: hcloud-csi
|
||||||
|
app.kubernetes.io/component: controller
|
||||||
|
automountServiceAccountToken: true
|
||||||
|
---
|
||||||
|
# Source: hcloud-csi/templates/core/storageclass.yaml
|
||||||
|
kind: StorageClass
|
||||||
|
apiVersion: storage.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: hcloud-volumes
|
||||||
|
annotations:
|
||||||
|
storageclass.kubernetes.io/is-default-class: "true"
|
||||||
|
provisioner: csi.hetzner.cloud
|
||||||
|
volumeBindingMode: WaitForFirstConsumer
|
||||||
|
allowVolumeExpansion: true
|
||||||
|
reclaimPolicy: "Delete"
|
||||||
|
---
|
||||||
|
# Source: hcloud-csi/templates/controller/clusterrole.yaml
|
||||||
|
kind: ClusterRole
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: hcloud-csi-controller
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: hcloud-csi
|
||||||
|
app.kubernetes.io/instance: hcloud-csi
|
||||||
|
app.kubernetes.io/component: controller
|
||||||
|
rules:
|
||||||
|
# attacher
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [persistentvolumes]
|
||||||
|
verbs: [get, list, watch, update, patch]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [nodes]
|
||||||
|
verbs: [get, list, watch]
|
||||||
|
- apiGroups: [csi.storage.k8s.io]
|
||||||
|
resources: [csinodeinfos]
|
||||||
|
verbs: [get, list, watch]
|
||||||
|
- apiGroups: [storage.k8s.io]
|
||||||
|
resources: [csinodes]
|
||||||
|
verbs: [get, list, watch]
|
||||||
|
- apiGroups: [storage.k8s.io]
|
||||||
|
resources: [volumeattachments]
|
||||||
|
verbs: [get, list, watch, update, patch]
|
||||||
|
- apiGroups: [storage.k8s.io]
|
||||||
|
resources: [volumeattachments/status]
|
||||||
|
verbs: [patch]
|
||||||
|
# provisioner
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [secrets]
|
||||||
|
verbs: [get, list]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [persistentvolumes]
|
||||||
|
verbs: [get, list, watch, create, delete, patch]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [persistentvolumeclaims, persistentvolumeclaims/status]
|
||||||
|
verbs: [get, list, watch, update, patch]
|
||||||
|
- apiGroups: [storage.k8s.io]
|
||||||
|
resources: [storageclasses]
|
||||||
|
verbs: [get, list, watch]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [events]
|
||||||
|
verbs: [list, watch, create, update, patch]
|
||||||
|
- apiGroups: [snapshot.storage.k8s.io]
|
||||||
|
resources: [volumesnapshots]
|
||||||
|
verbs: [get, list]
|
||||||
|
- apiGroups: [snapshot.storage.k8s.io]
|
||||||
|
resources: [volumesnapshotcontents]
|
||||||
|
verbs: [get, list]
|
||||||
|
# resizer
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [pods]
|
||||||
|
verbs: [get, list, watch]
|
||||||
|
# node
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [events]
|
||||||
|
verbs: [get, list, watch, create, update, patch]
|
||||||
|
---
|
||||||
|
# Source: hcloud-csi/templates/controller/clusterrolebinding.yaml
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: hcloud-csi-controller
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: hcloud-csi
|
||||||
|
app.kubernetes.io/instance: hcloud-csi
|
||||||
|
app.kubernetes.io/component: controller
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: hcloud-csi-controller
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: hcloud-csi-controller
|
||||||
|
namespace: "kube-system"
|
||||||
|
---
|
||||||
|
# Source: hcloud-csi/templates/controller/service.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: hcloud-csi-controller-metrics
|
||||||
|
namespace: "kube-system"
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: hcloud-csi
|
||||||
|
app.kubernetes.io/instance: hcloud-csi
|
||||||
|
app.kubernetes.io/component: controller
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: metrics
|
||||||
|
port: 9189
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: hcloud-csi
|
||||||
|
app.kubernetes.io/instance: hcloud-csi
|
||||||
|
app.kubernetes.io/component: controller
|
||||||
|
---
|
||||||
|
# Source: hcloud-csi/templates/node/service.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: hcloud-csi-node-metrics
|
||||||
|
namespace: "kube-system"
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: hcloud-csi
|
||||||
|
app.kubernetes.io/instance: hcloud-csi
|
||||||
|
app.kubernetes.io/component: node
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: metrics
|
||||||
|
port: 9189
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: hcloud-csi
|
||||||
|
app.kubernetes.io/instance: hcloud-csi
|
||||||
|
app.kubernetes.io/component: node
|
||||||
|
---
|
||||||
|
# Source: hcloud-csi/templates/node/daemonset.yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: hcloud-csi-node
|
||||||
|
namespace: "kube-system"
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: hcloud-csi
|
||||||
|
app.kubernetes.io/instance: hcloud-csi
|
||||||
|
app.kubernetes.io/component: node
|
||||||
|
app: hcloud-csi
|
||||||
|
spec:
|
||||||
|
updateStrategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: hcloud-csi
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: hcloud-csi
|
||||||
|
app.kubernetes.io/instance: hcloud-csi
|
||||||
|
app.kubernetes.io/component: node
|
||||||
|
app: hcloud-csi
|
||||||
|
spec:
|
||||||
|
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: instance.hetzner.cloud/is-root-server
|
||||||
|
operator: NotIn
|
||||||
|
values:
|
||||||
|
- "true"
|
||||||
|
tolerations:
|
||||||
|
- effect: NoExecute
|
||||||
|
operator: Exists
|
||||||
|
- effect: NoSchedule
|
||||||
|
operator: Exists
|
||||||
|
- key: CriticalAddonsOnly
|
||||||
|
operator: Exists
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 1001
|
||||||
|
initContainers:
|
||||||
|
containers:
|
||||||
|
- name: csi-node-driver-registrar
|
||||||
|
image: registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.7.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
args:
|
||||||
|
- --kubelet-registration-path=/var/lib/kubelet/plugins/csi.hetzner.cloud/socket
|
||||||
|
volumeMounts:
|
||||||
|
- name: plugin-dir
|
||||||
|
mountPath: /run/csi
|
||||||
|
- name: registration-dir
|
||||||
|
mountPath: /registration
|
||||||
|
resources:
|
||||||
|
limits: {}
|
||||||
|
requests: {}
|
||||||
|
- name: liveness-probe
|
||||||
|
image: registry.k8s.io/sig-storage/livenessprobe:v2.9.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /run/csi
|
||||||
|
name: plugin-dir
|
||||||
|
resources:
|
||||||
|
limits: {}
|
||||||
|
requests: {}
|
||||||
|
- name: hcloud-csi-driver
|
||||||
|
image: docker.io/hetznercloud/hcloud-csi-driver:v2.6.0 # x-release-please-version
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: [/bin/hcloud-csi-driver-node]
|
||||||
|
volumeMounts:
|
||||||
|
- name: kubelet-dir
|
||||||
|
mountPath: /var/lib/kubelet
|
||||||
|
mountPropagation: "Bidirectional"
|
||||||
|
- name: plugin-dir
|
||||||
|
mountPath: /run/csi
|
||||||
|
- name: device-dir
|
||||||
|
mountPath: /dev
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
env:
|
||||||
|
- name: CSI_ENDPOINT
|
||||||
|
value: unix:///run/csi/socket
|
||||||
|
- name: METRICS_ENDPOINT
|
||||||
|
value: "0.0.0.0:9189"
|
||||||
|
- name: ENABLE_METRICS
|
||||||
|
value: "true"
|
||||||
|
ports:
|
||||||
|
- containerPort: 9189
|
||||||
|
name: metrics
|
||||||
|
- name: healthz
|
||||||
|
protocol: TCP
|
||||||
|
containerPort: 9808
|
||||||
|
resources:
|
||||||
|
limits: {}
|
||||||
|
requests: {}
|
||||||
|
livenessProbe:
|
||||||
|
failureThreshold: 5
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 2
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 3
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: healthz
|
||||||
|
volumes:
|
||||||
|
- name: kubelet-dir
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/kubelet
|
||||||
|
type: Directory
|
||||||
|
- name: plugin-dir
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/kubelet/plugins/csi.hetzner.cloud/
|
||||||
|
type: DirectoryOrCreate
|
||||||
|
- name: registration-dir
|
||||||
|
hostPath:
|
||||||
|
path: /var/lib/kubelet/plugins_registry/
|
||||||
|
type: Directory
|
||||||
|
- name: device-dir
|
||||||
|
hostPath:
|
||||||
|
path: /dev
|
||||||
|
type: Directory
|
||||||
|
---
|
||||||
|
# Source: hcloud-csi/templates/controller/deployment.yaml
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: hcloud-csi-controller
|
||||||
|
namespace: "kube-system"
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: hcloud-csi
|
||||||
|
app.kubernetes.io/instance: hcloud-csi
|
||||||
|
app.kubernetes.io/component: controller
|
||||||
|
app: hcloud-csi-controller
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: hcloud-csi-controller
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: hcloud-csi
|
||||||
|
app.kubernetes.io/instance: hcloud-csi
|
||||||
|
app.kubernetes.io/component: controller
|
||||||
|
app: hcloud-csi-controller
|
||||||
|
spec:
|
||||||
|
serviceAccountName: hcloud-csi-controller
|
||||||
|
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 1001
|
||||||
|
initContainers:
|
||||||
|
containers:
|
||||||
|
- name: csi-attacher
|
||||||
|
image: registry.k8s.io/sig-storage/csi-attacher:v4.1.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
resources:
|
||||||
|
limits: {}
|
||||||
|
requests: {}
|
||||||
|
args:
|
||||||
|
- --default-fstype=ext4
|
||||||
|
volumeMounts:
|
||||||
|
- name: socket-dir
|
||||||
|
mountPath: /run/csi
|
||||||
|
|
||||||
|
- name: csi-resizer
|
||||||
|
image: registry.k8s.io/sig-storage/csi-resizer:v1.7.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
resources:
|
||||||
|
limits: {}
|
||||||
|
requests: {}
|
||||||
|
volumeMounts:
|
||||||
|
- name: socket-dir
|
||||||
|
mountPath: /run/csi
|
||||||
|
|
||||||
|
- name: csi-provisioner
|
||||||
|
image: registry.k8s.io/sig-storage/csi-provisioner:v3.4.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
resources:
|
||||||
|
limits: {}
|
||||||
|
requests: {}
|
||||||
|
args:
|
||||||
|
- --feature-gates=Topology=true
|
||||||
|
- --default-fstype=ext4
|
||||||
|
volumeMounts:
|
||||||
|
- name: socket-dir
|
||||||
|
mountPath: /run/csi
|
||||||
|
|
||||||
|
- name: liveness-probe
|
||||||
|
image: registry.k8s.io/sig-storage/livenessprobe:v2.9.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
resources:
|
||||||
|
limits: {}
|
||||||
|
requests: {}
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /run/csi
|
||||||
|
name: socket-dir
|
||||||
|
|
||||||
|
- name: hcloud-csi-driver
|
||||||
|
image: docker.io/hetznercloud/hcloud-csi-driver:v2.6.0 # x-release-please-version
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: [/bin/hcloud-csi-driver-controller]
|
||||||
|
env:
|
||||||
|
- name: CSI_ENDPOINT
|
||||||
|
value: unix:///run/csi/socket
|
||||||
|
- name: METRICS_ENDPOINT
|
||||||
|
value: "0.0.0.0:9189"
|
||||||
|
- name: ENABLE_METRICS
|
||||||
|
value: "true"
|
||||||
|
- name: KUBE_NODE_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
apiVersion: v1
|
||||||
|
fieldPath: spec.nodeName
|
||||||
|
- name: HCLOUD_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: hcloud
|
||||||
|
key: token
|
||||||
|
resources:
|
||||||
|
limits: {}
|
||||||
|
requests: {}
|
||||||
|
ports:
|
||||||
|
- name: metrics
|
||||||
|
containerPort: 9189
|
||||||
|
- name: healthz
|
||||||
|
protocol: TCP
|
||||||
|
containerPort: 9808
|
||||||
|
livenessProbe:
|
||||||
|
failureThreshold: 5
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 2
|
||||||
|
successThreshold: 1
|
||||||
|
timeoutSeconds: 3
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: healthz
|
||||||
|
volumeMounts:
|
||||||
|
- name: socket-dir
|
||||||
|
mountPath: /run/csi
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: socket-dir
|
||||||
|
emptyDir: {}
|
||||||
|
---
|
||||||
|
# Source: hcloud-csi/templates/core/csidriver.yaml
|
||||||
|
apiVersion: storage.k8s.io/v1
|
||||||
|
kind: CSIDriver
|
||||||
|
metadata:
|
||||||
|
name: csi.hetzner.cloud
|
||||||
|
spec:
|
||||||
|
attachRequired: true
|
||||||
|
fsGroupPolicy: File
|
||||||
|
podInfoOnMount: true
|
||||||
|
volumeLifecycleModes:
|
||||||
|
- Persistent
|
|
@ -0,0 +1,11 @@
|
||||||
|
apiVersion: storage.k8s.io/v1
|
||||||
|
kind: StorageClass
|
||||||
|
metadata:
|
||||||
|
name: hcloud-volumes-encrypted
|
||||||
|
provisioner: csi.hetzner.cloud
|
||||||
|
reclaimPolicy: Delete
|
||||||
|
volumeBindingMode: WaitForFirstConsumer
|
||||||
|
allowVolumeExpansion: true
|
||||||
|
parameters:
|
||||||
|
csi.storage.k8s.io/node-publish-secret-name: encryption-secret
|
||||||
|
csi.storage.k8s.io/node-publish-secret-namespace: kube-system
|
|
@ -0,0 +1,7 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: encryption-secret
|
||||||
|
namespace: kube-system
|
||||||
|
stringData:
|
||||||
|
encryption-passphrase: $HETZNER_ENCRYPTION_PASSPHRASE
|
|
@ -2,9 +2,8 @@ kind: Ingress
|
||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
metadata:
|
metadata:
|
||||||
name: echo-ingress
|
name: echo-ingress
|
||||||
annotations:
|
|
||||||
kubernetes.io/ingress.class: "traefik"
|
|
||||||
spec:
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
rules:
|
rules:
|
||||||
- http:
|
- http:
|
||||||
paths:
|
paths:
|
||||||
|
|
|
@ -3,9 +3,9 @@ apiVersion: networking.k8s.io/v1
|
||||||
metadata:
|
metadata:
|
||||||
name: echo-ingress
|
name: echo-ingress
|
||||||
annotations:
|
annotations:
|
||||||
kubernetes.io/ingress.class: "traefik"
|
|
||||||
cert-manager.io/cluster-issuer: ${issuer_name}
|
cert-manager.io/cluster-issuer: ${issuer_name}
|
||||||
spec:
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
rules:
|
rules:
|
||||||
- host: ${fqdn}
|
- host: ${fqdn}
|
||||||
http:
|
http:
|
||||||
|
|
|
@ -41,3 +41,5 @@ spec:
|
||||||
effect: "NoSchedule"
|
effect: "NoSchedule"
|
||||||
service:
|
service:
|
||||||
ipFamilyPolicy: "PreferDualStack"
|
ipFamilyPolicy: "PreferDualStack"
|
||||||
|
annotations:
|
||||||
|
metallb.universe.tf/allow-shared-ip: shared-ip-service-group
|
||||||
|
|
|
@ -24,7 +24,7 @@ class ProvWithSudoKtTest {
|
||||||
fun test_ensureSudoWithoutPassword_local_Prov() {
|
fun test_ensureSudoWithoutPassword_local_Prov() {
|
||||||
|
|
||||||
mockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
mockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||||
every { getPasswordToConfigureSudoWithoutPassword() } returns Secret("testuserpw")
|
every { getPasswordToConfigureSudoWithoutPassword() } returns Secret("testuser")
|
||||||
|
|
||||||
// given
|
// given
|
||||||
val containerName = "prov-test-sudo-no-pw"
|
val containerName = "prov-test-sudo-no-pw"
|
||||||
|
@ -58,7 +58,7 @@ class ProvWithSudoKtTest {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
val containerName = "prov-test-sudo-no-pw-ssh"
|
val containerName = "prov-test-sudo-no-pw-ssh"
|
||||||
val password = Secret("testuserpw")
|
val password = Secret("testuser")
|
||||||
|
|
||||||
val prov = Prov.newInstance(
|
val prov = Prov.newInstance(
|
||||||
ContainerUbuntuHostProcessor(
|
ContainerUbuntuHostProcessor(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.domaindrivenarchitecture.provs.desktop.domain
|
package org.domaindrivenarchitecture.provs.desktop.domain
|
||||||
|
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
|
import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
|
||||||
import org.domaindrivenarchitecture.provs.desktop.infrastructure.installPpaFirefox
|
import org.domaindrivenarchitecture.provs.desktop.infrastructure.installPpaFirefox
|
||||||
import org.domaindrivenarchitecture.provs.desktop.infrastructure.verifyIdeSetup
|
import org.domaindrivenarchitecture.provs.desktop.infrastructure.verifyIdeSetup
|
||||||
import org.domaindrivenarchitecture.provs.desktop.infrastructure.verifyOfficeSetup
|
import org.domaindrivenarchitecture.provs.desktop.infrastructure.verifyOfficeSetup
|
||||||
|
@ -37,10 +38,8 @@ internal class DesktopServiceKtTest {
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Assertions.assertThrows(Exception::class.java) {
|
Assertions.assertThrows(Exception::class.java) {
|
||||||
prov.provisionDesktop(
|
prov.provisionDesktopCommand(
|
||||||
DesktopType.BASIC,
|
DesktopCliCommand(DesktopType.BASIC, TargetCliCommand("testuser@somehost"), null), DesktopConfig() // dummy data
|
||||||
gitUserName = "testuser",
|
|
||||||
gitEmail = "testuser@test.org",
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.getResourceAsText
|
import org.domaindrivenarchitecture.provs.framework.core.getResourceAsText
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.checkFile
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.*
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createDir
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createDirs
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.fileContainsText
|
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Disabled
|
import org.junit.jupiter.api.Disabled
|
||||||
|
@ -48,4 +46,17 @@ internal class DevOpsKtTest {
|
||||||
// then
|
// then
|
||||||
assertTrue(res.success)
|
assertTrue(res.success)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ContainerTest
|
||||||
|
fun installKubeconform() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = prov.installKubeconform()
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
assertTrue(prov.checkFile("/usr/local/bin/kubeconform"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.remote
|
import org.domaindrivenarchitecture.provs.framework.core.remote
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.KeyPair
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.KeyPair
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.configureGpgKeys
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.configureGpgKeys
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.gpgFingerprint
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.gpgFingerprint
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.GopassSecretSource
|
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Disabled
|
import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.*
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.*
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.privateGPGSnakeoilKey
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.PromptSecretSource
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.makeCurrentUserSudoerWithoutPasswordRequired
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||||
|
import org.domaindrivenarchitecture.provs.test_keys.publicGPGSnakeoilKey
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,24 +60,32 @@ internal class GopassKtTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled // Integrationtest; change user, host and keys, then remove this line to run this test
|
@Disabled // This is an integration test, which needs preparation:
|
||||||
|
// Pls change user, host and remote connection (choose connection either by password or by ssh key)
|
||||||
|
// then remove tag @Disabled to be able to run this test.
|
||||||
|
// PREREQUISITE: remote machine needs openssh-server installed
|
||||||
fun test_install_and_configure_Gopass_and_GopassBridgeJsonApi() {
|
fun test_install_and_configure_Gopass_and_GopassBridgeJsonApi() {
|
||||||
// settings to change
|
// host and user
|
||||||
val host = "192.168.56.135"
|
val host = "192.168.56.154"
|
||||||
val user = "xxx"
|
val user = "xxx"
|
||||||
val pubKey = GopassSecretSource("path-to/pub.key").secret()
|
|
||||||
val privateKey = GopassSecretSource("path-to/priv.key").secret()
|
|
||||||
|
|
||||||
// given
|
// connection by password
|
||||||
val prov = remote(host, user)
|
val pw = PromptSecretSource("Pw for $user").secret()
|
||||||
|
val prov = remote(host, user, pw)
|
||||||
|
prov.makeCurrentUserSudoerWithoutPasswordRequired(pw) // may be commented out if user can already sudo without password
|
||||||
|
|
||||||
|
// or alternatively use connection by ssh key if the public key is already available remotely
|
||||||
|
// val prov = remote(host, user)
|
||||||
|
|
||||||
|
|
||||||
|
val pubKey = Secret(publicGPGSnakeoilKey())
|
||||||
|
val privateKey = Secret(privateGPGSnakeoilKey())
|
||||||
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val res = prov.task {
|
val res = prov.task {
|
||||||
configureGpgKeys(
|
configureGpgKeys(
|
||||||
KeyPair(
|
KeyPair(pubKey, privateKey),
|
||||||
pubKey,
|
|
||||||
privateKey
|
|
||||||
),
|
|
||||||
trust = true,
|
trust = true,
|
||||||
skipIfExistin = true
|
skipIfExistin = true
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.checkFile
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
|
||||||
|
class GraalVMKtTest {
|
||||||
|
|
||||||
|
@ContainerTest
|
||||||
|
fun installGraalVM() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = prov.task {
|
||||||
|
installGraalVM()
|
||||||
|
installGraalVM() // test repeatability
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
assertTrue(GRAAL_VM_VERSION == prov.graalVMVersion())
|
||||||
|
assertTrue(prov.checkFile("/usr/local/bin/native-image"))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.docker.exitAndRmContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.local
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class HugoTest {
|
||||||
|
@ExtensiveContainerTest
|
||||||
|
fun test_installHugoByDeb() {
|
||||||
|
// given
|
||||||
|
local().exitAndRmContainer("provs_test")
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = prov.installHugoByDeb()
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_needsHugoInstall() {
|
||||||
|
// given
|
||||||
|
val hugoNull = null
|
||||||
|
val hugoLow = "hugo v0.0.0-abc+extended linux/amd64 BuildDate=0000-00-00 VendorInfo=snap:0.0.0"
|
||||||
|
val hugoMajHigh = "hugo v1.0.0-abc+extended linux/amd64 BuildDate=0000-00-00 VendorInfo=snap:1.0.0"
|
||||||
|
val hugoMinHigh = "hugo v0.1.0-abc+extended linux/amd64 BuildDate=0000-00-00 VendorInfo=snap:0.1.0"
|
||||||
|
val hugoPatHigh = "hugo v0.0.1-abc+extended linux/amd64 BuildDate=0000-00-00 VendorInfo=snap:0.0.1"
|
||||||
|
|
||||||
|
assertTrue(needsHugoInstall(hugoNull, hugoPatHigh))
|
||||||
|
assertTrue(needsHugoInstall(hugoLow, hugoPatHigh))
|
||||||
|
assertTrue(needsHugoInstall(hugoLow, hugoMinHigh))
|
||||||
|
assertTrue(needsHugoInstall(hugoLow, hugoMajHigh))
|
||||||
|
assertFalse(needsHugoInstall(hugoMajHigh, hugoLow))
|
||||||
|
assertFalse(needsHugoInstall(hugoMinHigh, hugoLow))
|
||||||
|
assertFalse(needsHugoInstall(hugoPatHigh, hugoLow))
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,10 @@ package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Disabled
|
||||||
|
|
||||||
internal class NextcloudClientTest {
|
internal class NextcloudClientTest {
|
||||||
|
@Disabled // test is hanging sometimes
|
||||||
@ExtensiveContainerTest
|
@ExtensiveContainerTest
|
||||||
fun test_installNextcloudClient() {
|
fun test_installNextcloudClient() {
|
||||||
// when
|
// when
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.remote
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.PromptSecretSource
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Disabled
|
import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
@ -9,14 +10,19 @@ import org.junit.jupiter.api.Test
|
||||||
internal class VSCodeKtTest {
|
internal class VSCodeKtTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled("Test currently not working, needs fix. VSC is installed by snapd which is not currently supported to run inside docker")
|
@Disabled("Remote testing by updating connection details below, then enable test and run it manually.")
|
||||||
fun installVSC() {
|
// Remark: VSCode installs with snap, which does not run in container and cannot be tested by container test.
|
||||||
|
fun installVSCode() {
|
||||||
// given
|
// given
|
||||||
val a = defaultTestContainer()
|
val prov = remote("192.168.56.153", "xx", PromptSecretSource("Remote password").secret()) // machine needs openssh-server installed and sudo possible without pw
|
||||||
a.aptInstall("xvfb libgbm-dev libasound2")
|
prov.aptInstall("xvfb libgbm-dev libasound2")
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val res = a.installVSC("python", "clojure")
|
val res = prov.task {
|
||||||
|
installVSCode("python", "clojure")
|
||||||
|
cmd("code -v")
|
||||||
|
cmd("codium -v")
|
||||||
|
}
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(res.success)
|
assertTrue(res.success)
|
||||||
|
|
|
@ -103,7 +103,8 @@ internal class UbuntuProvTest {
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertFalse(result.success)
|
assertFalse(result.success)
|
||||||
assertEquals("sudo: no tty present and no askpass program specified\n", result.err)
|
val expectedMsg = "a password is required"
|
||||||
|
assertTrue(result.err?.contains(expectedMsg) ?: false, "Error: [$expectedMsg] is not found in [${result.err}]")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -119,12 +120,12 @@ class UbuntuUserNeedsPasswordForSudo(private val userName: String = "testuser")
|
||||||
|
|
||||||
override fun imageText(): String {
|
override fun imageText(): String {
|
||||||
return """
|
return """
|
||||||
FROM ubuntu:18.04
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
RUN apt-get update && apt-get -y install sudo
|
RUN apt-get update && apt-get -y install sudo
|
||||||
RUN useradd -m $userName && echo "$userName:$userName" | chpasswd && adduser $userName sudo
|
RUN useradd -m $userName && echo "$userName:$userName" | chpasswd && usermod -aG sudo $userName
|
||||||
|
|
||||||
USER $userName
|
USER $userName
|
||||||
CMD /bin/bash
|
CMD /bin/bash
|
||||||
|
|
|
@ -34,7 +34,7 @@ class ContainerUbuntuHostProcessorTest {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
val containerName = "prov-test-ssh-with-container"
|
val containerName = "prov-test-ssh-with-container"
|
||||||
val password = Secret("testuserpw")
|
val password = Secret("testuser")
|
||||||
|
|
||||||
val prov = Prov.newInstance(
|
val prov = Prov.newInstance(
|
||||||
ContainerUbuntuHostProcessor(
|
ContainerUbuntuHostProcessor(
|
||||||
|
@ -57,8 +57,8 @@ class ContainerUbuntuHostProcessorTest {
|
||||||
val remoteProvBySsh = remote(ipOfContainer, "testuser", password)
|
val remoteProvBySsh = remote(ipOfContainer, "testuser", password)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val firstSessionResult = remoteProvBySsh.cmd("echo 1")
|
val firstSessionResult = remoteProvBySsh.cmd("echo 1") // connect (to container) by ssh via ip
|
||||||
val secondSessionResult = remoteProvBySsh.cmd("echo 1")
|
val secondSessionResult = remoteProvBySsh.cmd("echo 2") // second connect after first connection has been closed
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(firstSessionResult.success)
|
assertTrue(firstSessionResult.success)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import org.domaindrivenarchitecture.provs.framework.core.escapeProcentForPrintf
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.escapeSingleQuoteForShell
|
import org.domaindrivenarchitecture.provs.framework.core.escapeSingleQuoteForShell
|
||||||
import org.junit.jupiter.api.Assertions.*
|
import org.junit.jupiter.api.Assertions.*
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
|
||||||
internal class LocalProcessorTest {
|
internal class LocalProcessorTest {
|
||||||
|
@ -24,6 +25,18 @@ internal class LocalProcessorTest {
|
||||||
assertTrue(res.out == text)
|
assertTrue(res.out == text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun cmd_in_folder_where_program_was_started() {
|
||||||
|
// given
|
||||||
|
val prov = Prov.newInstance(LocalProcessor(false))
|
||||||
|
|
||||||
|
// when
|
||||||
|
val pwd = prov.cmd("pwd").outTrimmed
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(Paths.get("").toAbsolutePath().toString(), pwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun cmd_with_nested_shell_and_printf() {
|
fun cmd_with_nested_shell_and_printf() {
|
||||||
|
@ -59,7 +72,7 @@ internal class LocalProcessorTest {
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun cmd_forUnkownCommand_resultWithError() {
|
fun cmd_forUnknownCommand_resultWithError() {
|
||||||
// given
|
// given
|
||||||
val prov = Prov.newInstance()
|
val prov = Prov.newInstance()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.server.infrastructure
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSourceType
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSupplier
|
||||||
|
import org.domaindrivenarchitecture.provs.server.domain.hetzner_csi.HetznerCSIConfig
|
||||||
|
import org.domaindrivenarchitecture.provs.server.domain.k8s_grafana_agent.GrafanaAgentConfig
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class HetznerCSIRepositoryKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun findHetznerCSIConfig_returns_config() {
|
||||||
|
// when
|
||||||
|
val config = findHetznerCSIConfig(ConfigFileName("src/test/resources/k3s-server-config-with-hetzner.yaml"))
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(
|
||||||
|
HetznerCSIConfig(
|
||||||
|
hcloudApiToken = SecretSupplier(SecretSourceType.GOPASS, "path/to/apitoken"),
|
||||||
|
encryptionPassphrase = SecretSupplier(SecretSourceType.GOPASS, "path/to/encryption"),
|
||||||
|
), config
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun findHetznerCSIConfig_returns_null_if_no_hetzner_data_available() {
|
||||||
|
// when
|
||||||
|
val config = findHetznerCSIConfig(ConfigFileName("src/test/resources/k3s-server-config.yaml"))
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(null, config)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,7 @@
|
||||||
package org.domaindrivenarchitecture.provs.server.infrastructure
|
package org.domaindrivenarchitecture.provs.server.infrastructure
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.local
|
import org.domaindrivenarchitecture.provs.framework.core.local
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.remote
|
import org.domaindrivenarchitecture.provs.framework.core.remote
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createDir
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createSecretFile
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.PromptSecretSource
|
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.K3sConfig
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.K3sConfig
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.Node
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.Node
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.provisionK3s
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.provisionK3s
|
||||||
|
@ -14,6 +10,7 @@ import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.lang.Thread.sleep
|
import java.lang.Thread.sleep
|
||||||
|
|
||||||
|
|
||||||
class K3sKtTest {
|
class K3sKtTest {
|
||||||
|
|
||||||
@Test // Extensive test, takes several minutes
|
@Test // Extensive test, takes several minutes
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.server.infrastructure
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
|
||||||
|
class K9sKtTest {
|
||||||
|
|
||||||
|
@ContainerTest
|
||||||
|
fun installK9s() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = prov.task {
|
||||||
|
installK9s()
|
||||||
|
installK9s() // test repeatability
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
}
|
18
src/test/resources/k3s-server-config-with-hetzner.yaml
Normal file
18
src/test/resources/k3s-server-config-with-hetzner.yaml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
fqdn: statistics.test.meissa-gmbh.de
|
||||||
|
node:
|
||||||
|
ipv4: 162.55.164.138
|
||||||
|
ipv6: 2a01:4f8:c010:672f::1
|
||||||
|
certmanager:
|
||||||
|
email: admin@meissa-gmbh.de
|
||||||
|
letsencryptEndpoint: prod
|
||||||
|
echo: true
|
||||||
|
reprovision: true
|
||||||
|
|
||||||
|
|
||||||
|
hetzner:
|
||||||
|
hcloudApiToken:
|
||||||
|
source: "GOPASS" # PLAIN, GOPASS or PROMPT
|
||||||
|
parameter: "path/to/apitoken" # the api key for the hetzner cloud
|
||||||
|
encryptionPassphrase:
|
||||||
|
source: "GOPASS" # PLAIN, GOPASS or PROMPT
|
||||||
|
parameter: "path/to/encryption" # the encryption passphrase for created volumes
|
|
@ -16,7 +16,9 @@ const val defaultTestContainerName = "provs_test"
|
||||||
private lateinit var prov: Prov
|
private lateinit var prov: Prov
|
||||||
|
|
||||||
fun defaultTestContainer(startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE): Prov {
|
fun defaultTestContainer(startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE): Prov {
|
||||||
if (!::prov.isInitialized || !testLocal().containerRuns(defaultTestContainerName) || (startMode == ContainerStartMode.CREATE_NEW_KILL_EXISTING)) { prov = initDefaultTestContainer(startMode) }
|
if (!::prov.isInitialized || !testLocal().containerRuns(defaultTestContainerName) || (startMode == ContainerStartMode.CREATE_NEW_KILL_EXISTING)) {
|
||||||
|
prov = initDefaultTestContainer(startMode)
|
||||||
|
}
|
||||||
return prov
|
return prov
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue