Compare commits
262 commits
release-0.
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
c0985afacc | ||
|
e8d4826708 | ||
|
ca2176f67a | ||
|
445d12c849 | ||
|
9e1023b4b8 | ||
|
f0e7d6518f | ||
|
0d00f6f8c0 | ||
|
c649010491 | ||
|
659521e944 | ||
|
ffee8aaff4 | ||
|
b5a7b12794 | ||
|
7f8d4c8b97 | ||
|
e16c445b44 | ||
|
9a68de9135 | ||
|
98644199b5 | ||
|
3f181e07b4 | ||
|
ce93292336 | ||
|
10633eb856 | ||
|
da912dacf3 | ||
|
01ef388663 | ||
|
4cf36605c1 | ||
|
9affdbe04f | ||
|
b546b94410 | ||
|
2ee577a36f | ||
|
74f56aed9c | ||
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 | ||
|
8c6f25598d | ||
|
d4115992b0 | ||
|
4cf3ab358c | ||
|
d0e88c3bf4 | ||
|
f9c25a0e1a | ||
|
8191096794 | ||
|
1d64b4400e | ||
|
d43057bf9d | ||
|
bc38779b25 | ||
|
80ae171052 | ||
|
2bb986f80f | ||
bcc89ef408 | |||
20db8c4aca | |||
|
e85fa88bc4 | ||
|
7f2ebcd6e9 | ||
|
06a7cab974 | ||
|
1a1d7c2f6f | ||
|
eccf61b3d6 | ||
|
02ce6336a2 | ||
507475f40e | |||
33b38081d2 | |||
|
38ff640b00 | ||
|
fa9c570186 | ||
|
2cb39a82b8 | ||
|
a4c561649a | ||
|
1ae1a931b5 | ||
|
3be59e887d | ||
|
88e2cb5962 | ||
|
e039a68241 | ||
|
2fa6e106c7 | ||
|
97b5fdceb9 | ||
|
6b89e4e928 | ||
|
6cf5e75284 | ||
|
099a6f1cee | ||
|
948516aee7 | ||
|
647cfe5335 | ||
|
73f4d31459 | ||
|
42db8d8c92 | ||
|
5b6d94851e | ||
|
b268843ad4 | ||
|
790a5e7957 | ||
|
6b851cf783 | ||
|
84132db8f3 | ||
|
35f81320c1 | ||
|
14787b6f0f | ||
|
56334208be | ||
|
52a6582abe | ||
|
ac903c9f37 | ||
|
804fe0c83c | ||
|
40e2e3cd97 | ||
|
727b53aff9 | ||
|
1d12ea9c99 | ||
|
fc9cb72f1b | ||
|
d4f08cedc2 | ||
|
873a10c76f | ||
|
03a05a990a | ||
|
7f47c07b4d | ||
|
828b2684c7 | ||
|
ebea6dfad3 | ||
|
717ddc01ae | ||
|
37d2f4ff71 | ||
|
d66a79b299 | ||
|
d12633e43f | ||
|
9878aef9ae | ||
|
7107fa7e5d | ||
|
5cc9b32bf4 | ||
|
c22f943dee | ||
|
7ffff3ab13 | ||
|
9eef8e9f04 | ||
|
35e849783b | ||
|
5ed6187172 | ||
|
997e6d8407 | ||
|
f6ba3c9117 | ||
|
3903cf4a71 | ||
|
4eafbce5f4 | ||
|
98aa1306bf | ||
|
48e9b74b37 | ||
|
e060d584e9 | ||
|
a29d024ea3 | ||
|
42c92915f8 | ||
|
59163710af | ||
|
e373d327f3 | ||
fe6e48f6dd | |||
07f7b5a6de | |||
|
05450fed46 | ||
|
419bdcd5fc | ||
|
5572aa87ba | ||
|
a457c1d05e | ||
e56abd0c47 | |||
|
57adb756ad | ||
|
1ead864760 | ||
|
efb8fc8f8d | ||
|
7bcba91fd9 | ||
|
2fff923539 | ||
|
111d9951ed | ||
|
87b56fb0d2 | ||
|
17d3eb3491 | ||
|
3b18318921 | ||
|
11b13feb86 | ||
|
9ceb74515d | ||
|
f4da33dcb5 | ||
|
77e063842d | ||
|
e86efbc888 | ||
|
4452cf5d01 | ||
|
2071128371 | ||
|
eff4836d08 | ||
|
d484ae5fc6 | ||
3f4d5bb4d6 | |||
c1267ac17e | |||
e4e635d45b | |||
3a82fc707b | |||
a8b8c4e4db | |||
c1ceff0f94 | |||
|
e8fcdae778 | ||
|
32840cdd46 | ||
9d66a17a1b | |||
|
66470ed57a | ||
|
cd2ab71e6c | ||
9f92454e29 | |||
39c11c4a18 | |||
97109f01ab | |||
d9f3169708 | |||
|
15466c9706 | ||
|
02e9e7e404 | ||
|
c5762d6b06 | ||
|
343b3e0b5d | ||
|
5ae9cad15f | ||
|
54f7b1c2da | ||
|
c51d597e56 | ||
|
2f7eb1926e | ||
|
700132a163 | ||
|
a71adfbdc4 | ||
|
7a0d88ea74 | ||
|
e08d0d10a6 | ||
|
fc7500b41d | ||
|
685a9dc5cc | ||
|
14efdae873 | ||
|
18a0830c33 | ||
|
54ff18600e | ||
|
0437c92de4 | ||
|
3ca4a393bc | ||
|
83afc52889 | ||
|
4fd428109a | ||
|
71c7df921a | ||
|
6e609479c9 | ||
bf28d6306e | |||
90086e9c80 | |||
|
fbcdddf031 | ||
e230ed2176 | |||
59f3e7cfad | |||
0c648eeac7 | |||
59c5c8ba5a | |||
4063f56834 | |||
6633299f5b | |||
5cf5b87c91 | |||
e0131c2caf | |||
1c0c038969 | |||
48232826b1 | |||
723e9001fa | |||
bce6873851 | |||
51968263ff | |||
04865c6869 | |||
fc18e1d932 | |||
6bfd9588d8 | |||
0c24b09489 | |||
5f77b462a3 | |||
35f3aee26e | |||
72e5519cef | |||
5bd1432465 | |||
a763927f7d | |||
26904c0b57 | |||
8323b84bbe | |||
aef96be7f3 | |||
3998124f95 | |||
14c667caf3 |
110 changed files with 2846 additions and 1575 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,3 +9,4 @@
|
|||
/server-config.yaml
|
||||
/desktop-config.yaml
|
||||
/syspec-config.yaml
|
||||
/.kotlin/
|
||||
|
|
165
.gitlab-ci.yml
165
.gitlab-ci.yml
|
@ -1,47 +1,62 @@
|
|||
image: openjdk:11-jdk-slim
|
||||
|
||||
stages:
|
||||
- build
|
||||
- test
|
||||
- package
|
||||
- publish
|
||||
- release
|
||||
|
||||
before_script:
|
||||
- echo "---------- Start CI ----------"
|
||||
- export GRADLE_USER_HOME=`pwd`/.gradle
|
||||
- chmod +x gradlew
|
||||
- echo "------ commit tag ---------------"
|
||||
- echo $CI_COMMIT_TAG
|
||||
- echo $CI_COMMIT_REF_NAME
|
||||
|
||||
cache:
|
||||
.kotlin-job: &kotlin
|
||||
image: domaindrivenarchitecture/ddadevops-kotlin:4.10.7
|
||||
cache:
|
||||
key: ${CI_COMMIT_REF_SLUG}
|
||||
paths:
|
||||
- .gradle/wrapper
|
||||
- .gradle/caches
|
||||
before_script:
|
||||
- echo "---------- Start CI ----------"
|
||||
- export GRADLE_USER_HOME=`pwd`/.gradle
|
||||
- chmod +x gradlew
|
||||
- export RELEASE_ARTIFACT_TOKEN=$MEISSA_REPO_BUERO_RW
|
||||
- echo "------ commit info ---------------"
|
||||
- echo $CI_COMMIT_TAG
|
||||
- echo $CI_COMMIT_REF_NAME
|
||||
- echo "----------------------------------"
|
||||
|
||||
.tag_only: &tag_only
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||
when: never
|
||||
- if: '$CI_COMMIT_TAG =~ /^[0-9]+\.[0-9]+\.[0-9]+$/'
|
||||
|
||||
|
||||
build:
|
||||
<<: *kotlin
|
||||
stage: build
|
||||
script:
|
||||
- echo "---------- build stage ----------"
|
||||
- ./gradlew assemble
|
||||
- pyb build
|
||||
artifacts:
|
||||
paths:
|
||||
- build/libs/*.jar
|
||||
expire_in: 1 week
|
||||
|
||||
|
||||
variables:
|
||||
DOCKER_TLS_CERTDIR: "/certs"
|
||||
|
||||
|
||||
test:
|
||||
stage: test
|
||||
image: docker:latest
|
||||
image: docker:24.0.5
|
||||
services:
|
||||
- docker:dind
|
||||
- docker:24.0.5-dind
|
||||
dependencies:
|
||||
- build
|
||||
before_script:
|
||||
- echo "---------- BEFORE -------------"
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
||||
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY --username $CI_REGISTRY_USER --password-stdin
|
||||
script:
|
||||
- echo "---------- TEST -------------"
|
||||
- apk update && apk add bash openjdk11
|
||||
- apk update && apk add bash openjdk11 git
|
||||
- export JAVA_HOME=/usr/lib/jvm/java-11-openjdk
|
||||
- docker build --pull -t "$CI_REGISTRY_IMAGE" .
|
||||
- docker run --privileged -dit --name provs_test -v /var/run/docker.sock:/var/run/docker.sock $CI_REGISTRY_IMAGE
|
||||
|
@ -50,108 +65,46 @@ test:
|
|||
artifacts:
|
||||
when: on_failure
|
||||
paths:
|
||||
- build/reports/tests/test
|
||||
- build/reports/*
|
||||
reports:
|
||||
junit: build/test-results/test/TEST-*.xml
|
||||
|
||||
.fatjars:
|
||||
stage: package
|
||||
rules:
|
||||
# Do no allow manually triggered pipelines to prevent duplicates!
|
||||
# Instead rerun the pipeline created with the last push
|
||||
- if: $CI_PIPELINE_SOURCE != "push"
|
||||
when: never
|
||||
# Only execute when a valid version tag like v1.0, 2.3 or similar is given
|
||||
# Required is always one point like 1.0
|
||||
- if: $CI_COMMIT_TAG =~ /^v[0-9]+[.][0-9]+([.][0-9]+)?$/
|
||||
before_script:
|
||||
- echo $CI_JOB_ID
|
||||
# Writing FATJAR_JOB_ID variable to environment file, as variable is needed in the release stage.
|
||||
- echo FATJAR_JOB_ID=$CI_JOB_ID >> generate_executables.env
|
||||
script:
|
||||
- echo "---------- create fatjar ----------"
|
||||
- ./gradlew fatJarLatest
|
||||
- ./gradlew fatJarK3s
|
||||
artifacts:
|
||||
paths:
|
||||
- 'build/libs/provs.jar'
|
||||
- 'build/libs/provs-server.jar'
|
||||
reports:
|
||||
# To ensure we've access to this file in the next stage
|
||||
dotenv: generate_executables.env
|
||||
expire_in: 6 months
|
||||
|
||||
uberjar:
|
||||
package:
|
||||
<<: *kotlin
|
||||
stage: package
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE != "push"
|
||||
when: never
|
||||
- if: $CI_COMMIT_TAG =~ /^release-[0-9]+[.][0-9]+([.][0-9]+)?$/
|
||||
script:
|
||||
- ./gradlew -x assemble -x test -x jar uberjarDesktop
|
||||
- ./gradlew -x assemble -x test -x jar uberjarServer
|
||||
- ./gradlew -x assemble -x test -x jar uberjarSyspec
|
||||
- cd build/libs/
|
||||
- find . -type f -exec sha256sum {} \; | sort > sha256sum.lst
|
||||
- find . -type f -exec sha512sum {} \; | sort > sha512sum.lst
|
||||
artifacts:
|
||||
paths:
|
||||
- 'build/libs/provs-desktop.jar'
|
||||
- 'build/libs/provs-server.jar'
|
||||
- 'build/libs/provs-syspec.jar'
|
||||
- 'build/libs/sha256sum.lst'
|
||||
- 'build/libs/sha512sum.lst'
|
||||
expire_in: never
|
||||
|
||||
publish-snapshot-lib:
|
||||
stage: publish
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE != "push"
|
||||
when: never
|
||||
- if: $CI_COMMIT_TAG !~ /^release-[0-9]+[.][0-9]+([.][0-9]+)?$/
|
||||
script:
|
||||
- ./gradlew -x assemble -x test jar
|
||||
- ./gradlew -x assemble -x test publish
|
||||
- pyb package
|
||||
artifacts:
|
||||
paths:
|
||||
- build/libs/*.jar
|
||||
- build/libs/*.lst
|
||||
|
||||
publish-released-lib:
|
||||
stage: publish
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE != "push"
|
||||
when: never
|
||||
- if: $CI_COMMIT_TAG =~ /^release-[0-9]+[.][0-9]+([.][0-9]+)?$/
|
||||
script:
|
||||
- ./gradlew -x assemble -x test jar
|
||||
- ./gradlew -x assemble -x test publish
|
||||
artifacts:
|
||||
paths:
|
||||
- build/libs/*.jar
|
||||
|
||||
release:
|
||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||
publish-maven-package-to-gitlab:
|
||||
<<: *kotlin
|
||||
<<: *tag_only
|
||||
stage: publish
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE != "push"
|
||||
when: never
|
||||
- if: $CI_COMMIT_TAG =~ /^release-[0-9]+[.][0-9]+([.][0-9]+)?$/
|
||||
artifacts:
|
||||
paths:
|
||||
- 'build/libs/provs-desktop.jar'
|
||||
- 'build/libs/provs-server.jar'
|
||||
- 'build/libs/provs-syspec.jar'
|
||||
- 'build/libs/sha256sum.lst'
|
||||
- 'build/libs/sha512sum.lst'
|
||||
script:
|
||||
- apk --no-cache add curl
|
||||
- |
|
||||
release-cli create --name "Release $CI_COMMIT_TAG" --tag-name $CI_COMMIT_TAG \
|
||||
--assets-link "{\"name\":\"provs-desktop.jar\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/provs/-/jobs/${CI_JOB_ID}/artifacts/file/build/libs/provs-desktop.jar\"}" \
|
||||
--assets-link "{\"name\":\"provs-server.jar\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/provs/-/jobs/${CI_JOB_ID}/artifacts/file/build/libs/provs-server.jar\"}" \
|
||||
--assets-link "{\"name\":\"provs-syspec.jar\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/provs/-/jobs/${CI_JOB_ID}/artifacts/file/build/libs/provs-syspec.jar\"}" \
|
||||
--assets-link "{\"name\":\"sha256sum.lst\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/provs/-/jobs/${CI_JOB_ID}/artifacts/file/build/libs/sha256sum.lst\"}" \
|
||||
--assets-link "{\"name\":\"sha512sum.lst\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/provs/-/jobs/${CI_JOB_ID}/artifacts/file/build/libs/sha512sum.lst\"}" \
|
||||
- ./gradlew -x assemble -x test publishLibraryPublicationToGitlabRepository
|
||||
|
||||
|
||||
publish-maven-package-to-meissa:
|
||||
<<: *kotlin
|
||||
<<: *tag_only
|
||||
stage: publish
|
||||
allow_failure: true
|
||||
script:
|
||||
- ./gradlew -x assemble -x test publishLibraryPublicationToMeissaRepository
|
||||
|
||||
|
||||
release-to-meissa:
|
||||
<<: *kotlin
|
||||
<<: *tag_only
|
||||
stage: release
|
||||
allow_failure: true
|
||||
script:
|
||||
- pyb publish_release
|
||||
|
||||
after_script:
|
||||
- echo "---------- End CI ----------"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<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="MAIN_CLASS_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
|
||||
|
||||
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
|
||||
|
||||
USER testuser
|
||||
|
|
56
README.md
56
README.md
|
@ -1,20 +1,20 @@
|
|||
# provs
|
||||
[![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
|
||||
|
||||
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
|
||||
* performing system checks
|
||||
|
||||
Tasks can be run locally or remotely.
|
||||
|
||||
## Status
|
||||
|
||||
under development - though we already set up a few IDEs and servers with provs.
|
||||
|
||||
## Try out
|
||||
### 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
|
||||
* 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`
|
||||
* 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
|
||||
|
||||
|
@ -60,7 +61,6 @@ After having installed `provs-desktop.jar` (see prerequisites) execute:
|
|||
* `-o` for only executing one action, e.g.
|
||||
* `-o verify` for verifying your installation
|
||||
* `-o firefox` to install firefox from apt on ubuntu
|
||||
* `-o teams` to install MS-Teams
|
||||
|
||||
|
||||
#### Example
|
||||
|
@ -107,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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```bash
|
||||
|
@ -145,3 +163,27 @@ Or to get help for subcommands e.g.
|
|||
provs-desktop.jar ide -h
|
||||
provs-server.jar k3s -h
|
||||
```
|
||||
|
||||
## Development & mirrors
|
||||
|
||||
Development happens at: https://repo.prod.meissa.de/meissa/provs
|
||||
|
||||
Mirrors are:
|
||||
* https://gitlab.com/domaindrivenarchitecture/provs (CI issues and PR)
|
||||
* https://github.com/DomainDrivenArchitecture/provs
|
||||
|
||||
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)
|
120
build.gradle
120
build.gradle
|
@ -1,24 +1,24 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = "1.7.0"
|
||||
ext.kotlin_version_no = "1.8.20"
|
||||
ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID
|
||||
|
||||
repositories { mavenCentral() }
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "org.jetbrains.kotlin.jvm"
|
||||
apply plugin: "java-library"
|
||||
apply plugin: "java-test-fixtures"
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.jvm" version "$kotlin_version_no"
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version_no"
|
||||
id "java"
|
||||
id "java-test-fixtures"
|
||||
}
|
||||
apply plugin: "maven-publish"
|
||||
apply plugin: "kotlinx-serialization"
|
||||
|
||||
|
||||
version = "0.38.6-SNAPSHOT"
|
||||
group = "org.domaindrivenarchitecture.provs"
|
||||
version = "release-0.20.1"
|
||||
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
@ -26,16 +26,15 @@ repositories {
|
|||
|
||||
|
||||
java {
|
||||
// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(11)
|
||||
}
|
||||
}
|
||||
|
||||
jar {
|
||||
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
|
||||
}
|
||||
|
||||
|
||||
test {
|
||||
// set properties for the tests
|
||||
def propertiesForTests = ["testdockerwithoutsudo"]
|
||||
|
@ -61,39 +60,28 @@ compileJava.options.debugOptions.debugLevel = "source,lines,vars"
|
|||
compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars"
|
||||
compileTestJava.options.debugOptions.debugLevel = "source,lines,vars"
|
||||
|
||||
// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
|
||||
api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version")
|
||||
api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
|
||||
api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2")
|
||||
api("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version_no")
|
||||
api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4")
|
||||
|
||||
api('com.charleskorn.kaml:kaml:0.49.0')
|
||||
api('com.charleskorn.kaml:kaml:0.54.0')
|
||||
|
||||
api("org.slf4j:slf4j-api:1.7.36")
|
||||
api('ch.qos.logback:logback-classic:1.2.11')
|
||||
api('ch.qos.logback:logback-core:1.2.11')
|
||||
api('ch.qos.logback:logback-classic:1.4.14')
|
||||
api('ch.qos.logback:logback-core:1.4.14')
|
||||
|
||||
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlin_version")
|
||||
implementation("com.hierynomus:sshj:0.32.0")
|
||||
implementation("com.hierynomus:sshj:0.38.0")
|
||||
|
||||
implementation("aws.sdk.kotlin:s3:0.17.1-beta")
|
||||
|
||||
testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2")
|
||||
testFixturesApi('io.mockk:mockk:1.12.3')
|
||||
|
||||
testFixturesApi("org.junit.jupiter:junit-jupiter-api:5.8.2")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2")
|
||||
}
|
||||
|
||||
|
||||
task uberjarDesktop(type: Jar) {
|
||||
tasks.register('uberjarDesktop', Jar) {
|
||||
|
||||
from sourceSets.main.output
|
||||
|
||||
|
@ -114,7 +102,7 @@ task uberjarDesktop(type: Jar) {
|
|||
}
|
||||
|
||||
|
||||
task uberjarServer(type: Jar) {
|
||||
tasks.register('uberjarServer', Jar) {
|
||||
|
||||
from sourceSets.main.output
|
||||
|
||||
|
@ -135,7 +123,7 @@ task uberjarServer(type: Jar) {
|
|||
}
|
||||
|
||||
|
||||
task uberjarSyspec(type: Jar) {
|
||||
tasks.register('uberjarSyspec', Jar) {
|
||||
|
||||
from sourceSets.main.output
|
||||
|
||||
|
@ -159,7 +147,7 @@ def projectRoot = rootProject.projectDir
|
|||
|
||||
// copy jar to /usr/local/bin and make it executable
|
||||
// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper)
|
||||
task installlocally {
|
||||
tasks.register('installlocally') {
|
||||
dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec)
|
||||
doLast {
|
||||
exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") }
|
||||
|
@ -172,24 +160,37 @@ task installlocally {
|
|||
}
|
||||
}
|
||||
|
||||
task sourceJar(type: Jar, dependsOn: classes) {
|
||||
from sourceSets.main.allSource
|
||||
archiveClassifier.set("sources")
|
||||
|
||||
// create binaries and install into /usr/local/bin
|
||||
// PREREQUISITE: graalvm / native-image must be installed - see https://www.graalvm.org/
|
||||
tasks.register('binariesInstall') {
|
||||
dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec)
|
||||
doLast {
|
||||
println "Building binaries ..."
|
||||
exec { commandLine("sh", "-c", "cd build/libs/ && native-image --no-fallback --initialize-at-build-time=kotlin.DeprecationLevel -H:+UnlockExperimentalVMOptions -H:IncludeResources=\".*org/domaindrivenarchitecture/provs/.*(conf|ssh_config|sshd_config|sh|vimrc|xml|yaml)\" -jar provs-desktop.jar") }
|
||||
exec { commandLine("sh", "-c", "cd build/libs/ && native-image --no-fallback --initialize-at-build-time=kotlin.DeprecationLevel -H:+UnlockExperimentalVMOptions -H:IncludeResources=\".*org/domaindrivenarchitecture/provs/.*(conf|ssh_config|sshd_config|sh|vimrc|xml|yaml)\" -jar provs-server.jar") }
|
||||
exec { commandLine("sh", "-c", "cd build/libs/ && native-image --no-fallback --initialize-at-build-time=kotlin.DeprecationLevel -H:+UnlockExperimentalVMOptions -H:IncludeResources=\".*org/domaindrivenarchitecture/provs/.*(conf|ssh_config|sshd_config|sh|vimrc|xml|yaml)\" -jar provs-syspec.jar") }
|
||||
exec { commandLine("sh", "-c", "sudo cp build/libs/provs-desktop /usr/local/bin/") }
|
||||
exec { commandLine("sh", "-c", "sudo cp build/libs/provs-server /usr/local/bin/") }
|
||||
exec { commandLine("sh", "-c", "sudo cp build/libs/provs-syspec /usr/local/bin/") }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// publish to repo.prod.meissa.de with task "publishLibraryPublicationToMeissaRepository" -- (using pattern "publishLibraryPublicationTo<MAVEN REPOSITORY NAME>Repository")
|
||||
publishing {
|
||||
publications {
|
||||
library(MavenPublication) {
|
||||
groupId 'org.domaindrivenarchitecture'
|
||||
artifactId 'provs'
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
if (System.getenv("CI_JOB_TOKEN") != null) {
|
||||
// see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html
|
||||
maven {
|
||||
url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven"
|
||||
name "GitLab"
|
||||
name = "gitlab"
|
||||
url = "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven"
|
||||
credentials(HttpHeaderCredentials) {
|
||||
name = "Job-Token"
|
||||
value = System.getenv("CI_JOB_TOKEN")
|
||||
|
@ -198,8 +199,37 @@ publishing {
|
|||
header(HttpHeaderAuthentication)
|
||||
}
|
||||
}
|
||||
maven {
|
||||
name = "meissa"
|
||||
url = uri("https://repo.prod.meissa.de/api/packages/meissa/maven")
|
||||
|
||||
credentials(HttpHeaderCredentials) {
|
||||
name = "Authorization"
|
||||
def publishPackageTokenName = "MEISSA_PUBLISH_PACKAGE_TOKEN"
|
||||
if (System.getenv("CI_JOB_TOKEN") != null) {
|
||||
def tokenFromEnv = System.getenv(publishPackageTokenName)
|
||||
if (tokenFromEnv == null) {
|
||||
println "Error: $publishPackageTokenName not found"
|
||||
} else {
|
||||
mavenLocal()
|
||||
value = "token " + tokenFromEnv
|
||||
println "$publishPackageTokenName found - "
|
||||
}
|
||||
} else {
|
||||
// use project-property (define e.g. in "~/.gradle/gradle.properties") when not running in ci
|
||||
// you can create a token in gitea "Profile and Settings ... > Settings > Applications", Token Name, Select scopes (write:package) > "Generate Token"
|
||||
if (!project.hasProperty(publishPackageTokenName)) {
|
||||
// if token is missing, provide a dummy in order to avoid error "Could not get unknown property 'MEISSA_PUBLISH_PACKAGE_TOKEN' for Credentials [header: Authorization]" for other gradle tasks
|
||||
ext.MEISSA_PUBLISH_PACKAGE_TOKEN = "Token $publishPackageTokenName not provided in file \".gradle/gradle.properties\""
|
||||
println "Error: Token $publishPackageTokenName not found"
|
||||
} else {
|
||||
value = "token " + project.property(publishPackageTokenName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
authentication {
|
||||
header(HttpHeaderAuthentication)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
141
build.py
Normal file
141
build.py
Normal file
|
@ -0,0 +1,141 @@
|
|||
import os
|
||||
from subprocess import run
|
||||
from pybuilder.core import task, init
|
||||
from ddadevops import *
|
||||
|
||||
|
||||
name = "provs"
|
||||
PROJECT_ROOT_PATH = "."
|
||||
|
||||
|
||||
version = "0.38.3-dev"
|
||||
|
||||
|
||||
@init
|
||||
def initialize0(project):
|
||||
"""
|
||||
workaround to avoid prompt for gopass if no artifacts need to be uploaded
|
||||
usage: with option "-E ng" , e.g. "pyb -E artifacts patch_local"
|
||||
"""
|
||||
os.environ["RELEASE_ARTIFACT_TOKEN"] = "dummy" # avoids prompt for RELEASE_ARTIFACT_TOKEN
|
||||
|
||||
|
||||
@init(environments=["artifacts"])
|
||||
def initialize1(project):
|
||||
"""
|
||||
prompt for gopass if artifacts need to be uploaded
|
||||
usage: with option "-E artifacts" , e.g. "pyb -E artifacts dev"
|
||||
"""
|
||||
del os.environ["RELEASE_ARTIFACT_TOKEN"]
|
||||
|
||||
|
||||
@init
|
||||
def initialize2(project):
|
||||
input = {
|
||||
"name": name,
|
||||
"module": "notused",
|
||||
"stage": "notused",
|
||||
"project_root_path": PROJECT_ROOT_PATH,
|
||||
"build_types": [],
|
||||
"mixin_types": ["RELEASE"],
|
||||
"release_main_branch": "main",
|
||||
"release_primary_build_file": "build.gradle",
|
||||
"release_secondary_build_files": ["build.py"],
|
||||
# release artifacts
|
||||
"release_artifact_server_url": "https://repo.prod.meissa.de",
|
||||
"release_organisation": "meissa",
|
||||
"release_repository_name": name,
|
||||
"release_artifacts": [
|
||||
"build/libs/provs-server.jar",
|
||||
"build/libs/provs-desktop.jar",
|
||||
"build/libs/provs-syspec.jar",
|
||||
"build/libs/sha256sum.lst",
|
||||
"build/libs/sha512sum.lst",
|
||||
],
|
||||
}
|
||||
build = ReleaseMixin(project, input)
|
||||
build.initialize_build_dir()
|
||||
|
||||
|
||||
@task
|
||||
def dev(project):
|
||||
"""
|
||||
to avoid gopass prompt set RELEASE_ARTIFACT_TOKEN e.g.:
|
||||
RELEASE_ARTIFACT_TOKEN=xxx pyb dev
|
||||
"""
|
||||
run("./gradlew assemble", shell=True)
|
||||
|
||||
|
||||
@task
|
||||
def build(project):
|
||||
run("./gradlew assemble", shell=True)
|
||||
|
||||
|
||||
@task
|
||||
def patch(project):
|
||||
"""
|
||||
updates version to next patch level, creates a tag, creates new SNAPSHOT version,
|
||||
commits primary build file (build.gradle) and pushes to remote
|
||||
"""
|
||||
increase_version_number(project, "PATCH")
|
||||
release(project)
|
||||
|
||||
|
||||
@task
|
||||
def minor(project):
|
||||
"""
|
||||
updates version to next minor level, creates a tag, creates new SNAPSHOT version,
|
||||
commits primary build file (build.gradle) and pushes to remote
|
||||
"""
|
||||
increase_version_number(project, "MINOR")
|
||||
release(project)
|
||||
|
||||
|
||||
@task
|
||||
def major(project):
|
||||
"""
|
||||
updates version to next major level, creates a tag, creates new SNAPSHOT version,
|
||||
commits primary build file (build.gradle) and pushes to remote
|
||||
"""
|
||||
increase_version_number(project, "MAJOR")
|
||||
release(project)
|
||||
|
||||
|
||||
@task
|
||||
def tag(project):
|
||||
build = get_devops_build(project)
|
||||
build.tag_bump_and_push_release()
|
||||
|
||||
|
||||
@task
|
||||
def release(project):
|
||||
build = get_devops_build(project)
|
||||
build.prepare_release()
|
||||
tag(project)
|
||||
|
||||
|
||||
@task
|
||||
def package(project):
|
||||
run("./gradlew assemble -x test jar", shell=True)
|
||||
run("./gradlew assemble -x test uberjarDesktop", shell=True)
|
||||
run("./gradlew assemble -x test uberjarServer", shell=True)
|
||||
run("./gradlew assemble -x test uberjarSyspec", shell=True)
|
||||
run("cd build/libs/ && find . -type f -exec sha256sum {} \; | sort > sha256sum.lst", shell=True)
|
||||
run("cd build/libs/ && find . -type f -exec sha512sum {} \; | sort > sha512sum.lst", shell=True)
|
||||
|
||||
|
||||
@task
|
||||
def publish_release(project):
|
||||
""" creates a release in repo.meissa and uploads artifacts (jar-files and checksum files) """
|
||||
build = get_devops_build(project)
|
||||
build.publish_artifacts()
|
||||
|
||||
|
||||
@task
|
||||
def inst(project):
|
||||
run("./gradlew inst", shell=True)
|
||||
|
||||
|
||||
def increase_version_number(project, release_type):
|
||||
build = get_devops_build(project)
|
||||
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
|
||||
* 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.provisionK8s() = 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 ?
|
||||
## 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.
|
||||
|
||||
|
@ -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)
|
||||
* **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
|
||||
|
||||
In the following link you can find an example of a sequence diagram when provisioning a desktop:
|
||||
|
||||
[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.
|
||||
|
|
45
doc/GoForgejo_install.md
Normal file
45
doc/GoForgejo_install.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
# Go / forgejo Installation and Testing
|
||||
|
||||
## go install/update
|
||||
#### remove old version
|
||||
sudo rm -rf ~/go
|
||||
### download latest version and configure
|
||||
curl -OL https://go.dev/dl/go1.21.3.linux-amd64.tar.gz
|
||||
|
||||
# extract latest version to ~/go
|
||||
tar -C ~ -xzf go*.linux-amd64.tar.gz
|
||||
|
||||
# append path
|
||||
```
|
||||
(meissa) jem@meissa-ide-2023:~$ cat .bashrc.d/go.sh
|
||||
PATH=$PATH:$HOME/go/bin
|
||||
export PATH
|
||||
```
|
||||
|
||||
## VScode optional - TODO!?!
|
||||
"Go for VS Code v0.39.1"
|
||||
|
||||
## Testing forgejo
|
||||
full:
|
||||
make test
|
||||
|
||||
require node:
|
||||
make test-frontend
|
||||
|
||||
require go:
|
||||
make test-backend
|
||||
|
||||
#nvm - required to build forgejo frontend
|
||||
sudo apt remove nodejs
|
||||
sudo apt autoremove
|
||||
|
||||
adapt version to latest:
|
||||
curl o https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
|
||||
nvm install
|
||||
|
||||
optional:
|
||||
nvm alias default "latest"
|
||||
|
||||
## forgejo build
|
||||
TAGS="bindata" make build
|
||||
-> include make frontend & make backend //see details Makefile
|
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.
|
|
@ -1,35 +0,0 @@
|
|||
@startuml
|
||||
autonumber
|
||||
|
||||
Application -> Prov: create
|
||||
|
||||
activate Prov
|
||||
Application -> DesktopService.kt: provisionDesktop(prov, ...)
|
||||
DesktopService.kt -> Install.kt: aptInstall(prov, lambda=cmd "apt install", ..)
|
||||
Install.kt -> Prov: taskWithResult
|
||||
|
||||
activate Prov
|
||||
Prov -> Prov: evaluate
|
||||
|
||||
activate Prov
|
||||
Prov -> Prov: initProgress (bei level 0)
|
||||
Prov -> Prov: progress
|
||||
|
||||
activate Prov
|
||||
Prov -> Prov: lambda
|
||||
|
||||
activate Prov
|
||||
Prov -> Processor: exec
|
||||
deactivate Prov
|
||||
|
||||
Prov <-- Prov: ProvResult
|
||||
deactivate Prov
|
||||
|
||||
Prov -> Prov: endProgress (bei level 0)
|
||||
Prov -> Prov: printResults (bei level 0)
|
||||
|
||||
deactivate Prov
|
||||
deactivate Prov
|
||||
Install.kt <-- Prov: ProvResult
|
||||
|
||||
@enduml
|
47
doc/ProvTaskExecSequence.md
Normal file
47
doc/ProvTaskExecSequence.md
Normal file
|
@ -0,0 +1,47 @@
|
|||
```plantuml
|
||||
@startuml
|
||||
autonumber
|
||||
|
||||
participant Application
|
||||
participant DesktopService
|
||||
participant Install
|
||||
participant Prov
|
||||
participant Processor
|
||||
|
||||
Application -> Prov: create
|
||||
|
||||
activate Prov
|
||||
Application -> DesktopService: provisionDesktop(prov, ...)
|
||||
DesktopService -> Install: prov.aptInstall()
|
||||
Install -> Prov: taskWithResult( lambda = cmd("sudo apt install ...") )
|
||||
|
||||
activate Prov
|
||||
Prov -> Prov: evaluate
|
||||
|
||||
activate Prov
|
||||
Prov -> Prov: initProgress (if level 0)
|
||||
Prov -> Prov: progress
|
||||
|
||||
activate Prov
|
||||
Prov -> Prov: lambda
|
||||
|
||||
activate Prov
|
||||
|
||||
Prov -> Processor: exec
|
||||
Prov <-- Processor: exec
|
||||
|
||||
deactivate Prov
|
||||
deactivate Prov
|
||||
|
||||
Prov -> Prov: endProgress (if level 0)
|
||||
Prov -> Prov: printResults (if level 0)
|
||||
|
||||
deactivate Prov
|
||||
deactivate Prov
|
||||
|
||||
Install <-- Prov: ProvResult
|
||||
DesktopService <-- Install
|
||||
Application <-- DesktopService
|
||||
|
||||
@enduml
|
||||
```
|
|
@ -13,6 +13,7 @@ box "application" #LightBlue
|
|||
participant Application
|
||||
participant CliArgumentsParser
|
||||
participant DesktopCliCommand
|
||||
participant ProvWithSudo
|
||||
end box
|
||||
|
||||
box #White
|
||||
|
@ -21,8 +22,7 @@ participant "Prov (local or remote...)" as ProvInstance
|
|||
end box
|
||||
|
||||
box "domain" #LightGreen
|
||||
participant "DesktopService\n.provisionDesktopCommand" as DesktopService1
|
||||
participant "DesktopService\n.provisionDesktop" as DesktopService2
|
||||
participant "DesktopService"
|
||||
end box
|
||||
|
||||
box "infrastructure" #CornSilk
|
||||
|
@ -36,14 +36,18 @@ Application -> CliArgumentsParser : parseCommand
|
|||
Application -> DesktopCliCommand : isValid ?
|
||||
Application -> CliUtils : createProvInstance
|
||||
ProvInstance <- CliUtils : create
|
||||
Application -> DesktopService1 : provisionDesktopCommand ( provInstance, desktopCliCommand )
|
||||
DesktopService1 -> ConfigRepository : getConfig
|
||||
DesktopService1 -> DesktopService2 : provisionDesktop( config )
|
||||
|
||||
DesktopService2 -> Infrastructure_functions: Various calls like:
|
||||
DesktopService2 -> Infrastructure_functions: install ssh, gpg, git ...
|
||||
DesktopService2 -> Infrastructure_functions: installVirtualBoxGuestAdditions
|
||||
DesktopService2 -> Infrastructure_functions: configureNoSwappiness, ...
|
||||
Application -> ProvWithSudo : ensureSudoWithoutPassword
|
||||
Application -> DesktopService : provisionDesktopCommand ( provInstance, desktopCliCommand )
|
||||
|
||||
DesktopService -> ConfigRepository : getConfig
|
||||
|
||||
DesktopService -> DesktopService : provisionDesktop( config )
|
||||
|
||||
DesktopService -> Infrastructure_functions: Various calls like:
|
||||
DesktopService -> Infrastructure_functions: install ssh, gpg, git ...
|
||||
DesktopService -> Infrastructure_functions: installVirtualBoxGuestAdditions
|
||||
DesktopService -> Infrastructure_functions: configureNoSwappiness, ...
|
||||
|
||||
@enduml
|
||||
```
|
|
@ -2,8 +2,6 @@ This repository holds the documentation of the provs framework.
|
|||
|
||||
# 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"
|
||||
|
||||
Configuration management tools are usually classified as either **imperative** or **declarative**.
|
||||
|
|
|
@ -5,7 +5,7 @@ release-1.2 or release-1.2.3
|
|||
|
||||
I.e.: release-X.X.Z where X, Y, Z are the major, minor resp. the patch level of the release. Z can be omitted.
|
||||
|
||||
**Note:** Such kind of release tags should only be applied to commits in the master branch.
|
||||
**Note:** Such kind of release tags should only be applied to commits in the main branch.
|
||||
|
||||
```
|
||||
#adjust [version]
|
||||
|
|
38
doc/dev/ADRServicesImplementationStatic.md
Normal file
38
doc/dev/ADRServicesImplementationStatic.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
# ADR: We implement domain services static
|
||||
|
||||
Domain services can be implemented either as object (and composed like done in spring / example1 ) or with extension
|
||||
function and composed static (see example2).
|
||||
|
||||
## example1
|
||||
```kotlin
|
||||
class DesktopServie(val aptApi: AptApi, val prov: Prov) {
|
||||
fun provisionIdeDesktop(onlyModules: List<String>? = null) {
|
||||
prov.task {
|
||||
if (onlyModules == null) {
|
||||
aptApi.aptInstall(OPEN_VPM)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## example2
|
||||
```kotlin
|
||||
fun Prov.provisionIdeDesktop(onlyModules: List<String>? = null) {
|
||||
if (onlyModules == null) {
|
||||
aptInstall(OPEN_VPM)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Decission
|
||||
|
||||
We use extension function and composed static.
|
||||
|
||||
## Reason
|
||||
|
||||
1. Similar to composed objects we can easily mock `aptInstall` in tests. Both solutions are equivalent.
|
||||
2. Inheritance in case of composed objects we can solve by static composition.
|
||||
3. Object composition we can solve by static composition.
|
||||
|
||||
There is no reason left to change the current implementd pattern.
|
77
doc/dev/architecture.md
Normal file
77
doc/dev/architecture.md
Normal file
|
@ -0,0 +1,77 @@
|
|||
|
||||
## Initialization
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor user
|
||||
participant app as Application
|
||||
participant ds as DesktopService
|
||||
participant gtr as KnownHost
|
||||
participant pa as CliArgumentsParser
|
||||
participant cr as DesktopConfigRepository
|
||||
participant ut as CliUtils
|
||||
participant su as ProvsWithSudo
|
||||
|
||||
user ->> app: main
|
||||
activate app
|
||||
app ->> pa: parseCommands
|
||||
app ->> cr: getConfig(configFileName)
|
||||
app ->> ut: createProvInstance(cmd.target)
|
||||
app ->> su: ensureSudoWithoutPassword(cmd.target.remoteTarget()?.password)
|
||||
app ->> ds: provisionDesktopCommand(cmd, config)
|
||||
activate ds
|
||||
ds ->> gtr: values()
|
||||
gtr -->> ds: List(KnownHost)
|
||||
deactivate ds
|
||||
deactivate app
|
||||
```
|
||||
|
||||
## Domain
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
|
||||
namespace configuration {
|
||||
|
||||
class TargetCliCommand {
|
||||
val target: String,
|
||||
val passwordInteractive: Boolean = false
|
||||
}
|
||||
|
||||
class ConfigFileName {
|
||||
fileName: String
|
||||
}
|
||||
}
|
||||
|
||||
namespace desktop {
|
||||
|
||||
class DesktopCliCommand {
|
||||
}
|
||||
|
||||
class DesktopConfig {
|
||||
val ssh: SshKeyPairSource? = null,
|
||||
val gpg: KeyPairSource? = null,
|
||||
val gitUserName: String? = null,
|
||||
val gitEmail: String? = null,
|
||||
}
|
||||
|
||||
class DesktopType {
|
||||
val name: String
|
||||
}
|
||||
class DesktopOnlyModule {
|
||||
<<enum>>
|
||||
FIREFOX, VERIFY
|
||||
}
|
||||
|
||||
class KnownHost {
|
||||
hostName: String,
|
||||
hostKeys: List<HostKey>
|
||||
}
|
||||
}
|
||||
|
||||
DesktopCliCommand "1" *-- "1" DesktopType: type
|
||||
DesktopCliCommand "1" *-- "1" TargetCliCommand: target
|
||||
DesktopCliCommand "1" *-- "1" ConfigFileName: configFile
|
||||
DesktopCliCommand "1" *-- "..n" DesktopOnlyModule: onlyModules
|
||||
|
||||
```
|
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 |
|
@ -8,6 +8,8 @@ import org.domaindrivenarchitecture.provs.desktop.infrastructure.getConfig
|
|||
import org.domaindrivenarchitecture.provs.framework.core.cli.createProvInstance
|
||||
import org.domaindrivenarchitecture.provs.framework.core.cli.quit
|
||||
import java.io.FileNotFoundException
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
|
@ -21,22 +23,28 @@ fun main(args: Array<String>) {
|
|||
exitProcess(1)
|
||||
}
|
||||
|
||||
val config = if (cmd.configFile == null) DesktopConfig() else
|
||||
val defaultConfigFileName = "desktop-config.yaml"
|
||||
val config = if ((cmd.configFile == null) && !Files.isRegularFile(Path(defaultConfigFileName))) {
|
||||
println("ATTENTION: No config provided => using an empty config.")
|
||||
DesktopConfig()
|
||||
} else {
|
||||
val configFileName = cmd.configFile?.fileName ?: defaultConfigFileName
|
||||
try {
|
||||
getConfig(cmd.configFile.fileName)
|
||||
getConfig(configFileName)
|
||||
} catch (e: SerializationException) {
|
||||
println(
|
||||
"Error: File \"${cmd.configFile.fileName}\" has an invalid format and or invalid data."
|
||||
"Error: File \"${configFileName}\" has an invalid format and or invalid data."
|
||||
)
|
||||
null
|
||||
} catch (e: FileNotFoundException) {
|
||||
println(
|
||||
"Error: File\u001b[31m ${cmd.configFile.fileName} \u001b[0m was not found.\n" +
|
||||
"Pls copy file \u001B[31m desktop-config-example.yaml \u001B[0m to file \u001B[31m ${cmd.configFile.fileName} \u001B[0m " +
|
||||
"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 " +
|
||||
"and change the content according to your needs."
|
||||
)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
if (config == null) {
|
||||
println("No suitable config found.")
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package org.domaindrivenarchitecture.provs.desktop.domain
|
||||
|
||||
enum class DesktopOnlyModule {
|
||||
TEAMS, FIREFOX, VERIFY
|
||||
FIREFOX, VERIFY
|
||||
;
|
||||
|
||||
fun isIn(list: List<String>): Boolean {
|
||||
return list.any { it.equals(this.name, ignoreCase = true) }
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package org.domaindrivenarchitecture.provs.desktop.domain
|
||||
|
||||
import org.domaindrivenarchitecture.provs.desktop.domain.DesktopOnlyModule.FIREFOX
|
||||
import org.domaindrivenarchitecture.provs.desktop.domain.DesktopOnlyModule.VERIFY
|
||||
import org.domaindrivenarchitecture.provs.desktop.infrastructure.*
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.git.provisionGit
|
||||
|
@ -12,15 +14,23 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.provisionKeys
|
|||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.currentUserCanSudoWithoutPassword
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.whoami
|
||||
|
||||
|
||||
internal fun Prov.provisionDesktopCommand(cmd: DesktopCliCommand, conf: DesktopConfig) = task {
|
||||
|
||||
validatePrecondition()
|
||||
|
||||
val only = cmd.onlyModules
|
||||
if (only == null) {
|
||||
provisionDesktop(
|
||||
cmd.type,
|
||||
conf.ssh?.keyPair(),
|
||||
conf.gpg?.keyPair(),
|
||||
conf.gitUserName,
|
||||
conf.gitEmail,
|
||||
cmd.onlyModules
|
||||
)
|
||||
} else {
|
||||
provisionOnlyModules(cmd.type, only)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -38,24 +48,34 @@ internal fun Prov.provisionDesktop(
|
|||
gpg: KeyPair? = null,
|
||||
gitUserName: String? = null,
|
||||
gitEmail: String? = null,
|
||||
onlyModules: List<String>?
|
||||
) = task {
|
||||
validatePrecondition()
|
||||
provisionBasicDesktop(gpg, ssh, gitUserName, gitEmail, onlyModules)
|
||||
|
||||
provisionBasicDesktop(gpg, ssh, gitUserName, gitEmail)
|
||||
|
||||
if (desktopType == DesktopType.OFFICE) {
|
||||
provisionOfficeDesktop(onlyModules)
|
||||
if (onlyModules == null) {
|
||||
provisionOfficeDesktop()
|
||||
verifyOfficeSetup()
|
||||
}
|
||||
}
|
||||
if (desktopType == DesktopType.IDE) {
|
||||
if (onlyModules == null) {
|
||||
provisionOfficeDesktop()
|
||||
provisionIdeDesktop()
|
||||
verifyIdeSetup()
|
||||
} else {
|
||||
provisionIdeDesktop(onlyModules)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Prov.provisionOnlyModules(
|
||||
desktopType: DesktopType = DesktopType.BASIC,
|
||||
onlyModules: List<String>
|
||||
) = task {
|
||||
|
||||
if (FIREFOX.isIn(onlyModules)) {
|
||||
installPpaFirefox()
|
||||
}
|
||||
if (VERIFY.isIn(onlyModules)) {
|
||||
if (desktopType == DesktopType.OFFICE) {
|
||||
verifyOfficeSetup()
|
||||
} else if (desktopType == DesktopType.IDE) {
|
||||
verifyIdeSetup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,70 +86,13 @@ fun Prov.validatePrecondition() {
|
|||
}
|
||||
}
|
||||
|
||||
fun Prov.provisionIdeDesktop(onlyModules: List<String>? = null) {
|
||||
if (onlyModules == null) {
|
||||
aptInstall(OPEN_VPM)
|
||||
aptInstall(OPENCONNECT)
|
||||
aptInstall(VPNC)
|
||||
|
||||
// DevEnvs
|
||||
installDocker()
|
||||
aptInstall(JAVA)
|
||||
aptInstall(CLOJURE_TOOLS)
|
||||
installShadowCljs()
|
||||
installDevOps()
|
||||
provisionPython()
|
||||
|
||||
// IDEs
|
||||
installVSC("python", "clojure")
|
||||
installIntelliJ()
|
||||
} else if (onlyModules.contains(DesktopOnlyModule.VERIFY.name.lowercase())) {
|
||||
verifyIdeSetup()
|
||||
} else if (onlyModules.contains(DesktopOnlyModule.FIREFOX.name.lowercase())) {
|
||||
installFirefox()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused") // used in other projects
|
||||
fun Prov.provisionMSDesktop(onlyModules: List<String>?) {
|
||||
if (onlyModules == null) {
|
||||
installMsTeams()
|
||||
} else if (onlyModules.contains(DesktopOnlyModule.TEAMS.name.lowercase())) {
|
||||
installMsTeams()
|
||||
}
|
||||
}
|
||||
|
||||
fun Prov.provisionOfficeDesktop(onlyModules: List<String>? = null) {
|
||||
if (onlyModules == null) {
|
||||
aptInstall(ZIP_UTILS)
|
||||
aptInstall(SPELLCHECKING_DE)
|
||||
aptInstall(BROWSER)
|
||||
aptInstall(EMAIL_CLIENT)
|
||||
installDeltaChat()
|
||||
aptInstall(OFFICE_SUITE)
|
||||
installZimWiki()
|
||||
installNextcloudClient()
|
||||
aptInstall(COMPARE_TOOLS)
|
||||
|
||||
// optional as installation of these tools often fail and they are not considered mandatory
|
||||
optional {
|
||||
aptInstall(DRAWING_TOOLS)
|
||||
}
|
||||
} else if (onlyModules.contains(DesktopOnlyModule.VERIFY.name.lowercase())) {
|
||||
verifyOfficeSetup()
|
||||
} else if (onlyModules.contains(DesktopOnlyModule.FIREFOX.name.lowercase())) {
|
||||
installFirefox()
|
||||
}
|
||||
}
|
||||
|
||||
fun Prov.provisionBasicDesktop(
|
||||
gpg: KeyPair?,
|
||||
ssh: SshKeyPair?,
|
||||
gitUserName: String?,
|
||||
gitEmail: String?,
|
||||
onlyModules: List<String>?
|
||||
) {
|
||||
if (onlyModules == null) {
|
||||
aptInstall(KEY_MANAGEMENT)
|
||||
aptInstall(VERSION_MANAGEMENT)
|
||||
aptInstall(NETWORK_TOOLS)
|
||||
|
@ -141,7 +104,8 @@ fun Prov.provisionBasicDesktop(
|
|||
aptInstall(CLIP_TOOLS)
|
||||
aptPurge(
|
||||
"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("popularity-contest")
|
||||
|
@ -149,7 +113,7 @@ fun Prov.provisionBasicDesktop(
|
|||
provisionKeys(gpg, ssh)
|
||||
provisionGit(gitUserName ?: whoami(), gitEmail, gpg?.let { gpgFingerprint(it.publicKey.plain()) })
|
||||
|
||||
installFirefox()
|
||||
installPpaFirefox()
|
||||
installGopass()
|
||||
configureGopass(publicGpgKey = gpg?.publicKey)
|
||||
installGopassJsonApi()
|
||||
|
@ -161,7 +125,45 @@ fun Prov.provisionBasicDesktop(
|
|||
configureNoSwappiness()
|
||||
configureBash()
|
||||
installVirtualBoxGuestAdditions()
|
||||
} else if (onlyModules.contains(DesktopOnlyModule.FIREFOX.name.lowercase())) {
|
||||
installFirefox()
|
||||
}
|
||||
|
||||
fun Prov.provisionOfficeDesktop() {
|
||||
aptInstall(ZIP_UTILS)
|
||||
aptInstall(SPELLCHECKING_DE)
|
||||
aptInstall(BROWSER)
|
||||
aptInstall(EMAIL_CLIENT)
|
||||
installDeltaChat()
|
||||
aptInstall(OFFICE_SUITE)
|
||||
installZimWiki()
|
||||
// installNextcloudClient() might not install - might need fix and working test
|
||||
aptInstall(COMPARE_TOOLS)
|
||||
|
||||
// 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 {
|
||||
aptInstall(DRAWING_TOOLS)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Prov.provisionIdeDesktop() {
|
||||
aptInstall(OPEN_VPM)
|
||||
aptInstall(OPENCONNECT)
|
||||
aptInstall(VPNC)
|
||||
|
||||
// DevEnvs
|
||||
installDocker()
|
||||
aptInstall(JAVA)
|
||||
aptInstall(CLOJURE_TOOLS)
|
||||
installShadowCljs()
|
||||
installDevOps()
|
||||
provisionPython()
|
||||
installHugoByDeb()
|
||||
|
||||
// IDEs
|
||||
installIntelliJ()
|
||||
|
||||
installKubeconform()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package org.domaindrivenarchitecture.provs.desktop.domain
|
||||
|
||||
typealias HostKey = String
|
||||
|
||||
/**
|
||||
* Represents a known host for ssh connections.
|
||||
*
|
||||
* @param hostName domain name or ip
|
||||
* @param port (optional) to be specified if different from default port 22
|
||||
* @param hostKeys list of keys, where each should contain separated by space: 1. keytype, 2. key and 3. (optionally) a comment
|
||||
*
|
||||
* See: https://man7.org/linux/man-pages/man8/sshd.8.html#SSH_KNOWN_HOSTS_FILE_FORMAT
|
||||
*/
|
||||
open class KnownHost(
|
||||
val hostName: String,
|
||||
val port: Int? = null,
|
||||
val hostKeys: List<HostKey>
|
||||
) {
|
||||
constructor(hostName: String, hostKeys: List<HostKey>) : this(hostName, null, hostKeys)
|
||||
|
||||
companion object {
|
||||
val GITHUB = KnownHost(
|
||||
"github.com",
|
||||
listOf(
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl",
|
||||
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=",
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=",
|
||||
)
|
||||
)
|
||||
val GITLAB = KnownHost(
|
||||
"gitlab.com",
|
||||
listOf(
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf",
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9",
|
||||
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=",
|
||||
)
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
protected val values = listOf(GITHUB, GITLAB)
|
||||
|
||||
fun values(): List<KnownHost> {
|
||||
return values
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package org.domaindrivenarchitecture.provs.desktop.domain
|
||||
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.addKnownHost
|
||||
|
||||
|
||||
fun Prov.addKnownHosts(knownHosts: List<KnownHost> = KnownHost.values()) = task {
|
||||
for (knownHost in knownHosts) {
|
||||
addKnownHost(knownHost, verifyKeys = true)
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ import java.io.FileWriter
|
|||
* Returns DesktopConfig; data for config is read from specified file.
|
||||
* Throws exceptions FileNotFoundException, SerializationException if file is not found resp. cannot be parsed.
|
||||
*/
|
||||
fun getConfig(filename: String = "desktop-config.yaml"): DesktopConfig = readFromFile(filename).yamlToType()
|
||||
fun getConfig(filename: String): DesktopConfig = readFromFile(filename).yamlToType()
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
|
|
|
@ -15,15 +15,13 @@ fun Prov.installDevOps() = task {
|
|||
installTerraform()
|
||||
installKubectlAndTools()
|
||||
installYq()
|
||||
installAwsCredentials()
|
||||
installDevOpsFolder()
|
||||
installGraalVM()
|
||||
}
|
||||
|
||||
|
||||
fun Prov.installYq(
|
||||
version: String = "4.13.2",
|
||||
sha256sum: String = "d7c89543d1437bf80fee6237eadc608d1b121c21a7cbbe79057d5086d74f8d79"
|
||||
): ProvResult = task {
|
||||
) = task {
|
||||
val path = "/usr/bin/"
|
||||
val filename = "yq"
|
||||
if (!checkFile(path + filename)) {
|
||||
|
@ -40,7 +38,7 @@ fun Prov.installYq(
|
|||
}
|
||||
}
|
||||
|
||||
fun Prov.installKubectlAndTools(): ProvResult = task {
|
||||
fun Prov.installKubectlAndTools() = task {
|
||||
|
||||
task("installKubectl") {
|
||||
if (!checkFile(KUBE_CONFIG_CONTEXT_SCRIPT)) {
|
||||
|
@ -51,35 +49,61 @@ fun Prov.installKubectlAndTools(): ProvResult = task {
|
|||
}
|
||||
}
|
||||
|
||||
task("installKubeconform") {
|
||||
|
||||
installKubeconform()
|
||||
}
|
||||
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/
|
||||
val kubectlVersion = "1.23.0"
|
||||
val kubectlVersion = "1.27.4"
|
||||
val tmpDir = "~/tmp"
|
||||
|
||||
// prerequisites -- see https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/
|
||||
optional {
|
||||
cmd("sudo apt-get update")
|
||||
}
|
||||
aptInstall("apt-transport-https ca-certificates curl")
|
||||
createDir(tmpDir)
|
||||
downloadFromURL(
|
||||
"https://dl.k8s.io/release/v$kubectlVersion/bin/linux/amd64/kubectl",
|
||||
path = tmpDir,
|
||||
// from https://dl.k8s.io/v1.23.0/bin/linux/amd64/kubectl.sha256
|
||||
sha256sum = "2d0f5ba6faa787878b642c151ccb2c3390ce4c1e6c8e2b59568b3869ba407c4f"
|
||||
// from https://dl.k8s.io/v1.27.4/bin/linux/amd64/kubectl.sha256
|
||||
sha256sum = "4685bfcf732260f72fce58379e812e091557ef1dfc1bc8084226c7891dd6028f"
|
||||
)
|
||||
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)
|
||||
createDir(".bashrc.d")
|
||||
createFileFromResource(KUBE_CONFIG_CONTEXT_SCRIPT, "kubectl.sh", RESOURCE_PATH)
|
||||
}
|
||||
|
||||
fun Prov.installDevopsScripts() {
|
||||
fun Prov.installDevopsScripts() = task {
|
||||
|
||||
task("install ssh helper") {
|
||||
createFileFromResource(
|
||||
|
@ -121,7 +145,7 @@ fun Prov.installDevopsScripts() {
|
|||
}
|
||||
}
|
||||
|
||||
fun Prov.installTerraform(): ProvResult = task {
|
||||
fun Prov.installTerraform() = task {
|
||||
val dir = "/usr/lib/tfenv/"
|
||||
|
||||
if (!checkDir(dir)) {
|
||||
|
@ -131,47 +155,6 @@ fun Prov.installTerraform(): ProvResult = task {
|
|||
cmd("ln -s " + dir + "bin/* /usr/local/bin", sudo = true)
|
||||
}
|
||||
cmd("tfenv install", sudo = true)
|
||||
cmd("tfenv install latest:^1.0.8", sudo = true)
|
||||
cmd("tfenv use latest:^1.0.8", 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()
|
||||
}
|
||||
|
||||
fun Prov.installDevOpsFolder(): ProvResult = task {
|
||||
|
||||
val dir = "~/.devops/"
|
||||
|
||||
if (!checkDir(dir)) {
|
||||
createDirs(dir)
|
||||
}
|
||||
|
||||
cmd("tfenv install latest:^1.4.6", sudo = true)
|
||||
cmd("tfenv use latest:^1.4.6", sudo = true)
|
||||
}
|
||||
|
|
|
@ -1,42 +1,61 @@
|
|||
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.addTextToFile
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
||||
import java.io.File
|
||||
import org.domaindrivenarchitecture.provs.framework.core.ProvResult
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.checkFile
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFile
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstallFromPpa
|
||||
|
||||
|
||||
/**
|
||||
* Installs non-snap firefox, removing a firefox snap-installation if existing
|
||||
* Installs ppa firefox (i.e. non-snap), removing snap-firefox if existing.
|
||||
*/
|
||||
fun Prov.installFirefox() = task {
|
||||
fun Prov.installPpaFirefox() = taskWithResult {
|
||||
|
||||
// inspired by: https://www.omgubuntu.co.uk/2022/04/how-to-install-firefox-deb-apt-ubuntu-22-04
|
||||
// inspired by: https://wiki.ubuntuusers.de/Firefox/Installation/PPA/
|
||||
|
||||
task("remove snap firefox") {
|
||||
if (chk("snap list | grep firefox")) {
|
||||
val unattendeUpgradesForPpaFirefox = "/etc/apt/apt.conf.d/51unattended-upgrades-firefox"
|
||||
|
||||
val preCondition = checkFile(unattendeUpgradesForPpaFirefox)
|
||||
if (preCondition) {
|
||||
return@taskWithResult ProvResult(true, out = "Firefox already installed with ppa")
|
||||
}
|
||||
|
||||
cmd("sudo apt-get -qy remove firefox", sudo = true)
|
||||
optional("remove snap firefox") {
|
||||
cmd("snap remove firefox", sudo = true)
|
||||
}
|
||||
}
|
||||
|
||||
aptInstall("software-properties-common")
|
||||
cmd("add-apt-repository -y ppa:mozillateam/ppa", sudo = true)
|
||||
createFile("/etc/apt/preferences.d/mozillateam", mozillaTeamFileContent, sudo = true)
|
||||
|
||||
// set prio in order to use ppa-firefox above snap
|
||||
addTextToFile(
|
||||
"\nPackage: *\n" +
|
||||
"Pin: release o=LP-PPA-mozillateam\n" +
|
||||
"Pin-Priority: 1001\n",
|
||||
File("/etc/apt/preferences.d/mozilla-firefox"),
|
||||
aptInstallFromPpa("mozillateam", "ppa", "firefox")
|
||||
|
||||
createFile(
|
||||
unattendeUpgradesForPpaFirefox,
|
||||
"Unattended-Upgrade::Allowed-Origins:: \"LP-PPA-mozillateam:\${distro_codename}\";\n",
|
||||
sudo = true
|
||||
)
|
||||
|
||||
addTextToFile(
|
||||
"""Unattended-Upgrade::Allowed-Origins:: "LP-PPA-mozillateam:${'$'}{distro_codename}";""",
|
||||
File("/etc/apt/preferences.d/mozilla-firefox"),
|
||||
sudo = true
|
||||
)
|
||||
|
||||
aptInstall("firefox")
|
||||
cmd("apt-get upgrade -y --allow-downgrades firefox", sudo = true)
|
||||
}
|
||||
|
||||
|
||||
private val mozillaTeamFileContent = """
|
||||
Package: *
|
||||
Pin: release o=LP-PPA-mozillateam
|
||||
Pin-Priority: 100
|
||||
|
||||
Package: firefox*
|
||||
Pin: release o=LP-PPA-mozillateam
|
||||
Pin-Priority: 1001
|
||||
|
||||
Package: firefox*
|
||||
Pin: release o=Ubuntu
|
||||
Pin-Priority: -1
|
||||
|
||||
Package: thunderbird*
|
||||
Pin: release o=LP-PPA-mozillateam
|
||||
Pin-Priority: 1001
|
||||
|
||||
Package: thunderbird*
|
||||
Pin: release o=Ubuntu
|
||||
Pin-Priority: -1
|
||||
""".trimIndent()
|
|
@ -11,9 +11,10 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.web.base.downloadFrom
|
|||
|
||||
|
||||
fun Prov.installGopass(
|
||||
version: String = "1.12.7",
|
||||
version: String = "1.15.13", // NOTE: when adjusting, pls also adjust checksum below and version of gopass bridge json api
|
||||
enforceVersion: Boolean = false,
|
||||
sha256sum: String = "0824d5110ff1e68bff1ba10c1be63acb67cb1ad8e3bccddd6b6fc989608beca8" // checksum for sha256sum version 8.30 (e.g. ubuntu 20.04)
|
||||
// from https://github.com/gopasspw/gopass/releases/tag/v1.15.13
|
||||
sha256sum: String = "409ed5617e64fa2c781d5e2807ba7fcd65bc383a4e110f410f90b590e51aec55"
|
||||
) = taskWithResult {
|
||||
|
||||
if (isPackageInstalled("gopass") && !enforceVersion) {
|
||||
|
@ -45,16 +46,16 @@ fun Prov.installGopass(
|
|||
|
||||
fun Prov.configureGopass(gopassRootFolder: String? = null, publicGpgKey: Secret? = null) = taskWithResult {
|
||||
|
||||
val configFile = ".config/gopass/config.yml"
|
||||
|
||||
if (checkFile(configFile)) {
|
||||
return@taskWithResult ProvResult(true, out = "Gopass already configured in file $configFile")
|
||||
}
|
||||
val configFile = ".config/gopass/config"
|
||||
|
||||
if ((gopassRootFolder != null) && (!gopassRootFolder.startsWith("/"))) {
|
||||
return@taskWithResult ProvResult(false, err = "Gopass cannot be initialized with a relative path or path starting with ~ ($gopassRootFolder)")
|
||||
}
|
||||
|
||||
if(!fileContainsText(configFile,"share/gopass/stores/root")){
|
||||
return@taskWithResult ProvResult(true, out = "Gopass already configured in file $configFile")
|
||||
}
|
||||
|
||||
val defaultRootFolder = userHome() + ".password-store"
|
||||
val gopassRoot = gopassRootFolder ?: defaultRootFolder
|
||||
|
||||
|
@ -71,30 +72,41 @@ fun Prov.configureGopass(gopassRootFolder: String? = null, publicGpgKey: Secret?
|
|||
}
|
||||
|
||||
|
||||
fun Prov.gopassMountStore(storeName: String, path: String) = task {
|
||||
fun Prov.gopassMountStore(storeName: String, path: String) = taskWithResult {
|
||||
val mounts = cmdNoEval("gopass mounts").out ?: return@taskWithResult ProvResult(false, err = "could not determine gopass mounts")
|
||||
if (mounts.contains(storeName)) {
|
||||
ProvResult(true, out = "Store $storeName already mounted.")
|
||||
} else {
|
||||
cmd("gopass mounts add $storeName $path")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Prov.gopassInitStoreFolder(path: String, gpgFingerprint: String? = null ) = task {
|
||||
createFile("$path/.gpg-id", gpgFingerprint ?: "_replace_this_by_a_fingerprint_of_a_public_gpg_key_")
|
||||
if (!checkDir(".git", path)) {
|
||||
cmd("git init", path)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun gopassConfig(gopassRoot: String): String {
|
||||
return """
|
||||
autoclip: true
|
||||
autoimport: true
|
||||
cliptimeout: 45
|
||||
exportkeys: true
|
||||
nocolor: false
|
||||
nopager: false
|
||||
notifications: true
|
||||
parsing: true
|
||||
path: $gopassRoot
|
||||
safecontent: false
|
||||
mounts: {}
|
||||
""".trimIndent() + "\n"
|
||||
[core]
|
||||
parsing = true
|
||||
exportkeys = true
|
||||
autoclip = true
|
||||
showsafecontent = false
|
||||
nopager = false
|
||||
cliptimeout = 45
|
||||
notifications = true
|
||||
autoimport = true
|
||||
[age]
|
||||
usekeychain = false
|
||||
[mounts]
|
||||
path = $gopassRoot
|
||||
"""
|
||||
.trimIndent() + "\n"
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.web.base.downloadFrom
|
|||
|
||||
|
||||
fun Prov.downloadGopassBridge() = task {
|
||||
val version = "0.9.0"
|
||||
val filename = "gopass_bridge-${version}-fx.xpi"
|
||||
// Attention: when changing the version, you also need to change the number after /file/ in the download url below
|
||||
val filename = "gopass_bridge-0.9.0-fx.xpi"
|
||||
val downloadDir = "${userHome()}Downloads/"
|
||||
|
||||
createDirs(downloadDir)
|
||||
|
@ -18,13 +18,14 @@ fun Prov.downloadGopassBridge() = task {
|
|||
"-L https://addons.mozilla.org/firefox/downloads/file/3630534/$filename",
|
||||
downloadDir + filename
|
||||
)
|
||||
// needs manual installation with: firefox Downloads/gopass_bridge-0.8.0-fx.xpi
|
||||
// needs manual installation with: firefox Downloads/gopass_bridge-0.X.0-fx.xpi
|
||||
}
|
||||
|
||||
fun Prov.installGopassJsonApi() = taskWithResult {
|
||||
// see https://github.com/gopasspw/gopass-jsonapi
|
||||
val gopassJsonApiVersion = "1.11.1"
|
||||
val requiredGopassVersion = "1.12.7"
|
||||
// from https://github.com/gopasspw/gopass-jsonapi/releases/tag/v1.15.13
|
||||
val sha256sum = "3162ab558301645024325ce2e419c1d67900e1faf95dc1774a36f1ebfc76389f"
|
||||
val gopassJsonApiVersion = "1.15.13"
|
||||
val requiredGopassVersion = "1.15.13"
|
||||
val filename = "gopass-jsonapi_${gopassJsonApiVersion}_linux_amd64.deb"
|
||||
val downloadUrl = "-L https://github.com/gopasspw/gopass-jsonapi/releases/download/v$gopassJsonApiVersion/$filename"
|
||||
val downloadDir = "${userHome()}Downloads"
|
||||
|
@ -35,7 +36,7 @@ fun Prov.installGopassJsonApi() = taskWithResult {
|
|||
if (checkGopassVersion(requiredGopassVersion)) {
|
||||
aptInstall("git gnupg2") // required dependencies
|
||||
createDir(downloadDir)
|
||||
downloadFromURL(downloadUrl, filename, downloadDir)
|
||||
downloadFromURL(downloadUrl, filename, downloadDir, sha256sum = sha256sum)
|
||||
cmd("dpkg -i $downloadDir/$filename", sudo = true)
|
||||
} else {
|
||||
ProvResult(
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
||||
|
||||
fun Prov.installMsTeams() = task {
|
||||
aptInstall("curl gnupg2")
|
||||
cmd("curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -")
|
||||
cmd("sudo sh -c 'echo \"deb [arch=amd64] https://packages.microsoft.com/repos/ms-teams stable main\" > /etc/apt/sources.list.d/teams.list'")
|
||||
cmd("sudo apt-get update") // apt needs update
|
||||
aptInstall("teams")
|
||||
}
|
|
@ -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 JAVA = "openjdk-8-jdk openjdk-11-jdk openjdk-17-jdk jarwrapper"
|
||||
val JAVA = "openjdk-17-jdk jarwrapper"
|
||||
|
||||
val DRAWING_TOOLS = "inkscape dia openboard graphviz"
|
||||
|
||||
|
|
|
@ -7,36 +7,62 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInsta
|
|||
import java.io.File
|
||||
|
||||
|
||||
fun Prov.provisionPython() = task {
|
||||
fun Prov.provisionPython(venvHome: String? = "~/.venv/meissa") = task {
|
||||
installPython3()
|
||||
configureVenv()
|
||||
installPybuilder()
|
||||
installRestClient()
|
||||
installJupyterlab()
|
||||
if (venvHome != null) { configureVenv(venvHome) }
|
||||
installPybuilder(venvHome)
|
||||
installRestClient(venvHome)
|
||||
installJupyterlab(venvHome)
|
||||
installLinters(venvHome)
|
||||
installAsciinema(venvHome)
|
||||
installPyTest(venvHome)
|
||||
}
|
||||
|
||||
fun Prov.installPython3(): ProvResult = task {
|
||||
aptInstall("python3-venv python3-pip")
|
||||
}
|
||||
|
||||
fun Prov.configureVenv(): ProvResult = task {
|
||||
val venvHome = "~/.venv/meissa"
|
||||
cmd("python3 -m venv " + venvHome)
|
||||
cmd("source " + venvHome + "/bin/activate")
|
||||
createSymlink(File(venvHome + "/bin/activate"), File("~/.bashrc.d/venv.sh"))
|
||||
cmd("pip3 install pip --upgrade")
|
||||
fun Prov.configureVenv(venvHome: String): ProvResult = task {
|
||||
cmd("python3 -m venv $venvHome")
|
||||
createSymlink(File("$venvHome/bin/activate"), File("~/.bashrc.d/venv.sh"))
|
||||
pipInstall("pip --upgrade", venvHome)
|
||||
}
|
||||
|
||||
fun Prov.installPybuilder(): ProvResult = task {
|
||||
cmd("pip3 install pybuilder ddadevops pypandoc mockito coverage unittest-xml-reporting deprecation" +
|
||||
" python_terraform dda_python_terraform boto3 pyyaml ")
|
||||
cmd("pip3 install --upgrade ddadevops")
|
||||
fun Prov.installPybuilder(venvHome: String? = null): ProvResult = task {
|
||||
pipInstall("pybuilder ddadevops pypandoc mockito coverage unittest-xml-reporting deprecation" +
|
||||
" python_terraform dda_python_terraform boto3 pyyaml packaging inflection",
|
||||
venvHome
|
||||
)
|
||||
pipInstall("--upgrade ddadevops", venvHome)
|
||||
}
|
||||
|
||||
fun Prov.installRestClient(): ProvResult = task {
|
||||
cmd("pip3 install requests")
|
||||
fun Prov.installRestClient(venvHome: String? = null): ProvResult = task {
|
||||
pipInstall("requests", venvHome)
|
||||
}
|
||||
|
||||
fun Prov.installJupyterlab(): ProvResult = task {
|
||||
cmd("pip3 install jupyterlab pandas matplotlib")
|
||||
fun Prov.installJupyterlab(venvHome: String? = null): ProvResult = task {
|
||||
pipInstall("jupyterlab pandas matplotlib", venvHome)
|
||||
}
|
||||
|
||||
fun Prov.installLinters(venvHome: String? = null): ProvResult = task {
|
||||
pipInstall("flake8 mypy pylint", venvHome)
|
||||
}
|
||||
fun Prov.installAsciinema(venvHome: String? = null): ProvResult = task {
|
||||
pipInstall("asciinema", venvHome)
|
||||
}
|
||||
|
||||
fun Prov.installPyTest(venvHome: String? = null): ProvResult = task {
|
||||
pipInstall("pytest", venvHome)
|
||||
}
|
||||
|
||||
private fun Prov.pipInstall(pkg: String, venvHome: String? = null) {
|
||||
cmd(activateVenvCommandPrefix(venvHome) + "pip3 install $pkg")
|
||||
}
|
||||
|
||||
private fun activateVenvCommandPrefix(venvHome: String?): String {
|
||||
return if (venvHome == null) {
|
||||
""
|
||||
} else {
|
||||
"source $venvHome/bin/activate && "
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,36 +6,33 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInsta
|
|||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.isPackageInstalled
|
||||
|
||||
|
||||
fun Prov.installVSC(vararg options: String) = task {
|
||||
val clojureExtensions =
|
||||
arrayListOf("betterthantomorrow.calva", "martinklepsch.clojure-joker-linter", "DavidAnson.vscode-markdownlint")
|
||||
val pythonExtensions = arrayListOf("ms-python.python")
|
||||
fun Prov.installVSCode(vararg options: String) = task {
|
||||
val clojureExtensions = setOf("betterthantomorrow.calva", "DavidAnson.vscode-markdownlint")
|
||||
val pythonExtensions = setOf("ms-python.python")
|
||||
|
||||
prerequisitesVSCinstall()
|
||||
installVSCodePrerequisites()
|
||||
|
||||
installVSCPackage()
|
||||
installVSCodiumPackage()
|
||||
|
||||
if (options.contains("clojure")) {
|
||||
installExtensionsCode(clojureExtensions)
|
||||
installExtensionsCodium(clojureExtensions)
|
||||
installVSCodeExtensions(clojureExtensions)
|
||||
installVSCodiumExtensions(clojureExtensions)
|
||||
}
|
||||
if (options.contains("python")) {
|
||||
installExtensionsCode(pythonExtensions)
|
||||
installExtensionsCodium(pythonExtensions)
|
||||
installVSCodeExtensions(pythonExtensions)
|
||||
installVSCodiumExtensions(pythonExtensions)
|
||||
}
|
||||
|
||||
provisionAdditionalToolsForVSCode()
|
||||
}
|
||||
|
||||
|
||||
private fun Prov.prerequisitesVSCinstall() = task {
|
||||
private fun Prov.installVSCodePrerequisites() = task {
|
||||
aptInstall("curl gpg unzip apt-transport-https")
|
||||
}
|
||||
|
||||
|
||||
@Suppress("unused") // only required for installation of vscode via apt
|
||||
private fun Prov.installVscWithApt() = task {
|
||||
private fun Prov.installVSCodeWithApt() = task {
|
||||
val packageName = "code"
|
||||
if (!isPackageInstalled(packageName)) {
|
||||
// see https://code.visualstudio.com/docs/setup/linux
|
||||
|
@ -65,7 +62,7 @@ private fun Prov.installVSCodiumPackage() = task {
|
|||
}
|
||||
|
||||
|
||||
private fun Prov.installExtensionsCode(extensions: List<String>) = optional {
|
||||
private fun Prov.installVSCodeExtensions(extensions: Set<String>) = optional {
|
||||
var res = ProvResult(true)
|
||||
for (ext in extensions) {
|
||||
res = cmd("code --install-extension $ext")
|
||||
|
@ -74,20 +71,11 @@ private fun Prov.installExtensionsCode(extensions: List<String>) = optional {
|
|||
// Settings can be found at $HOME/.config/Code/User/settings.json
|
||||
}
|
||||
|
||||
private fun Prov.installExtensionsCodium(extensions: List<String>) = optional {
|
||||
private fun Prov.installVSCodiumExtensions(extensions: Set<String>) = optional {
|
||||
var res = ProvResult(true)
|
||||
for (ext in extensions) {
|
||||
res = cmd("codium --install-extension $ext")
|
||||
res = ProvResult(res.success && cmd("codium --install-extension $ext").success)
|
||||
}
|
||||
res
|
||||
// Settings can be found at $HOME/.config/Code/User/settings.json
|
||||
}
|
||||
|
||||
|
||||
internal fun Prov.provisionAdditionalToolsForVSCode() = task {
|
||||
// Joker
|
||||
val version = "0.18.0"
|
||||
cmd("curl -Lo joker-${version}-linux-amd64.zip https://github.com/candid82/joker/releases/download/v${version}/joker-${version}-linux-amd64.zip")
|
||||
cmd("unzip joker-${version}-linux-amd64.zip")
|
||||
cmd("sudo mv joker /usr/local/bin/")
|
||||
// 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).
|
||||
* Returns success if no sub-tasks are called or if all subtasks finish with success.
|
||||
* 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 all sub-tasks finished with success or if no sub-tasks are called at all.
|
||||
*/
|
||||
fun task(name: String? = null, taskLambda: Prov.() -> Unit): ProvResult {
|
||||
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.
|
||||
* The returned result is included in the evaluation.
|
||||
* Same as task above but the lambda parameter must have a ProvResult as return type.
|
||||
* 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 {
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
return evaluate(ResultMode.FAILEXIT) { a() }
|
||||
return evaluate(ResultMode.FAILEXIT) { taskLambda() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -465,7 +467,7 @@ open class Prov protected constructor(
|
|||
}
|
||||
|
||||
fun printDeprecationWarningIfLevel0(methodName: String) {
|
||||
if (level == 0) {
|
||||
if (level == 0 && progressType != ProgressType.NONE) {
|
||||
println("WARNING: method $methodName should not be used at top-level, use method <session> instead.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ data class ProvResult(val success: Boolean,
|
|||
val exception: Exception? = null,
|
||||
val exit: String? = null) {
|
||||
|
||||
val outTrimmed: String? = out?.trim()
|
||||
|
||||
constructor(returnCode : Int) : this(returnCode == 0)
|
||||
|
||||
override fun toString(): String {
|
||||
|
|
|
@ -2,7 +2,6 @@ package org.domaindrivenarchitecture.provs.framework.core
|
|||
|
||||
import com.charleskorn.kaml.Yaml
|
||||
import com.charleskorn.kaml.YamlConfiguration
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import kotlinx.serialization.serializer
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
|
@ -18,15 +17,13 @@ fun writeToFile(fileName: String, text: String) {
|
|||
}
|
||||
|
||||
|
||||
@OptIn(InternalSerializationApi::class)
|
||||
inline fun <reified T : Any> String.yamlToType() = Yaml(configuration = YamlConfiguration(strictMode = false)).decodeFromString(
|
||||
T::class.serializer(),
|
||||
serializer<T>(),
|
||||
this
|
||||
)
|
||||
|
||||
|
||||
@OptIn(InternalSerializationApi::class)
|
||||
inline fun <reified T : Any> T.toYaml() = Yaml(configuration = YamlConfiguration(strictMode = false, encodeDefaults = false)).encodeToString(
|
||||
T::class.serializer(),
|
||||
serializer<T>(),
|
||||
this
|
||||
)
|
||||
|
|
|
@ -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.platforms.UbuntuProv
|
||||
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 "
|
||||
|
||||
|
@ -17,7 +17,7 @@ fun Prov.dockerProvideImage(image: DockerImage, skipIfExisting: Boolean = true,
|
|||
if (this is UbuntuProv) {
|
||||
return this.dockerProvideImagePlatform(image, skipIfExisting, sudo)
|
||||
} 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) {
|
||||
return this.dockerImageExistsPlatform(imageName, sudo)
|
||||
} 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) {
|
||||
return this.provideContainerPlatform(containerName, imageName, startMode, sudo, options, command)
|
||||
} 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) {
|
||||
return this.containerRunsPlatform(containerName, sudo)
|
||||
} 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) {
|
||||
return this.runContainerPlatform(containerName, imageName, sudo)
|
||||
} 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) {
|
||||
return this.exitAndRmContainerPlatform(containerName, sudo)
|
||||
} 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 {
|
||||
if (this is UbuntuProv) {
|
||||
return this.containerExecPlatform(containerName, cmd, sudo)
|
||||
} 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 {
|
||||
return """
|
||||
FROM ubuntu:20.04
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
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
|
||||
|
||||
USER $userName
|
||||
|
|
|
@ -7,10 +7,10 @@ import org.domaindrivenarchitecture.provs.framework.core.processors.Processor
|
|||
const val SHELL = "/bin/bash"
|
||||
|
||||
|
||||
class UbuntuProv internal constructor(
|
||||
open class UbuntuProv(
|
||||
processor: Processor = LocalProcessor(),
|
||||
name: String? = null,
|
||||
progressType: ProgressType
|
||||
progressType: ProgressType = ProgressType.BASIC
|
||||
) : Prov(processor, name, progressType) {
|
||||
|
||||
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 {
|
||||
return if (args.size == 1)
|
||||
return if (args.size == 1) {
|
||||
args[0].escapeAndEncloseByDoubleQuoteForShell()
|
||||
else
|
||||
if (args.size == 3 && SHELL.equals(args[0]) && "-c".equals(args[1]))
|
||||
} else {
|
||||
if (args.size == 3 && SHELL == args[0] && "-c" == args[1]) {
|
||||
SHELL + " -c " + args[2].escapeAndEncloseByDoubleQuoteForShell()
|
||||
else
|
||||
} else {
|
||||
args.joinToString(separator = " ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.IOException
|
||||
import java.nio.charset.Charset
|
||||
import java.nio.file.Paths
|
||||
|
||||
|
||||
private fun getOsName(): String {
|
||||
return System.getProperty("os.name")
|
||||
}
|
||||
|
||||
open class LocalProcessor : Processor {
|
||||
open class LocalProcessor(val useHomeDirAsWorkingDir: Boolean = true) : Processor {
|
||||
|
||||
companion object {
|
||||
@Suppress("JAVA_CLASS_ON_COMPANION")
|
||||
|
@ -26,7 +27,12 @@ open class LocalProcessor : Processor {
|
|||
|
||||
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 {
|
||||
|
|
|
@ -93,9 +93,9 @@ class RemoteProcessor(val host: InetAddress, val user: String, val password: Sec
|
|||
var session: Session? = null
|
||||
|
||||
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 err = BufferedReader(InputStreamReader(cmd.errorStream)).use { it.readText() }
|
||||
cmd.join(100, TimeUnit.SECONDS)
|
||||
|
|
|
@ -201,7 +201,7 @@ fun Prov.fileContentLargeFile(file: String, sudo: Boolean = false, chunkSize: In
|
|||
// check first chunk
|
||||
if (resultString == null) {
|
||||
if (!chunkResult.success) {
|
||||
return resultString
|
||||
return null
|
||||
} else {
|
||||
resultString = ""
|
||||
}
|
||||
|
@ -329,12 +329,16 @@ fun Prov.deleteDir(dir: String, path: String, sudo: Boolean = false): ProvResult
|
|||
if ("" == path) {
|
||||
throw RuntimeException("In deleteDir: path must not be empty.")
|
||||
}
|
||||
return if (checkDir(dir, path, sudo)) {
|
||||
val cmd = "cd $path && rmdir $dir"
|
||||
return if (!sudo) {
|
||||
if (!sudo) {
|
||||
cmd(cmd)
|
||||
} else {
|
||||
cmd(cmd.sudoizeCommand())
|
||||
}
|
||||
} else {
|
||||
ProvResult(true, out = "Dir to delete did not exist: $dir")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -403,7 +407,7 @@ fun Prov.fileSize(filename: String, sudo: Boolean = false): Int? {
|
|||
|
||||
|
||||
private fun ensureValidPosixFilePermission(posixFilePermission: String) {
|
||||
if (!Regex("^[0-7]{3}$").matches(posixFilePermission)) throw IllegalArgumentException("Wrong file permission ($posixFilePermission), permission must consist of 3 digits as e.g. 664")
|
||||
if (!Regex("^0?[0-7]{3}$").matches(posixFilePermission)) throw IllegalArgumentException("Wrong file permission ($posixFilePermission), permission must consist of 3 digits as e.g. 664")
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,9 +3,6 @@ package org.domaindrivenarchitecture.provs.framework.ubuntu.git.base
|
|||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.framework.core.ProvResult
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.*
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.KNOWN_HOSTS_FILE
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.trustHost
|
||||
import java.io.File
|
||||
|
||||
|
||||
/**
|
||||
|
@ -33,33 +30,14 @@ fun Prov.gitClone(
|
|||
ProvResult(true, out = "Repo [$pathWithBasename] already exists, but might not be up-to-date.")
|
||||
}
|
||||
} else {
|
||||
// create targetPath (if not yet existing)
|
||||
// create targetPath if not yet existing
|
||||
if (!checkDir(targetPath)) {
|
||||
createDirs(targetPath)
|
||||
}
|
||||
|
||||
// Note that all output of git clone on Linux is shown in stderr (normal progress info AND errors),
|
||||
// which might be confusing in the logfile.
|
||||
cmd("cd $targetPath && git clone $repoSource ${targetFolderName ?: ""}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Prov.trustGithub() = task {
|
||||
// current fingerprints from https://docs.github.com/en/github/authenticating-to-github/githubs-ssh-key-fingerprints
|
||||
val fingerprints = setOf(
|
||||
"SHA256:uNiVztksCsDhcc0u9e8BujQXVUpKZIDTMczCvj3tD2s github.com", // (RSA)
|
||||
"SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM github.com", // (ECDSA)
|
||||
"SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU github.com" // (Ed25519)
|
||||
)
|
||||
trustHost("github.com", fingerprints)
|
||||
}
|
||||
|
||||
|
||||
fun Prov.trustGitlab() = task {
|
||||
// entries for known_hosts from https://docs.gitlab.com/ee/user/gitlab_com/
|
||||
val gitlabFingerprints = """
|
||||
gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf
|
||||
gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9
|
||||
gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=
|
||||
""".trimIndent()
|
||||
addTextToFile("\n" + gitlabFingerprints + "\n", File(KNOWN_HOSTS_FILE))
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,10 @@ fun Prov.aptInstall(packages: String, ignoreAlreadyInstalled: Boolean = true): P
|
|||
if (!allInstalled) {
|
||||
if (!isPackageInstalled(packages)) {
|
||||
if (!aptInit) {
|
||||
optional {
|
||||
// may fail for some packages, but this should in general not be an issue
|
||||
cmd("sudo apt-get update")
|
||||
}
|
||||
cmd("sudo apt-get install -qy apt-utils")
|
||||
aptInit = true
|
||||
}
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
package org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base
|
||||
|
||||
import org.domaindrivenarchitecture.provs.desktop.domain.KnownHost
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.framework.core.ProvResult
|
||||
import org.domaindrivenarchitecture.provs.framework.core.echoCommandForText
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.checkFile
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createDir
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFile
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createSecretFile
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.*
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.SshKeyPair
|
||||
import java.io.File
|
||||
|
||||
|
||||
const val KNOWN_HOSTS_FILE = "~/.ssh/known_hosts"
|
||||
|
||||
/**
|
||||
* Installs ssh keys for active user; ssh filenames depend on the ssh key type, e.g. for public key file: "id_rsa.pub", "id_id_ed25519.pub", etc
|
||||
*/
|
||||
|
@ -23,72 +19,61 @@ fun Prov.configureSshKeys(sshKeys: SshKeyPair) = task {
|
|||
|
||||
|
||||
/**
|
||||
* Checks if the specified hostname or Ip is in a known_hosts file
|
||||
*
|
||||
* @return whether if was found
|
||||
* Checks if the specified host (domain name or IP) and (optional) port is contained in the known_hosts file
|
||||
*/
|
||||
fun Prov.isHostKnown(hostOrIp: String) : Boolean {
|
||||
return cmdNoEval("ssh-keygen -F $hostOrIp").out?.isNotEmpty() ?: false
|
||||
fun Prov.isKnownHost(hostOrIp: String, port: Int? = null): Boolean {
|
||||
val hostWithPotentialPort = port?.let { hostInKnownHostsFileFormat(hostOrIp, port) } ?: hostOrIp
|
||||
return cmdNoEval("ssh-keygen -F $hostWithPotentialPort").out?.isNotEmpty() ?: false
|
||||
}
|
||||
|
||||
fun hostInKnownHostsFileFormat(hostOrIp: String, port: Int? = null): String {
|
||||
return port?.let { "[$hostOrIp]:$port" } ?: hostOrIp
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds ssh keys for specified host (which also can be an ip-address) to ssh-file "known_hosts"
|
||||
* Either add the specified rsaFingerprints or - if null - add automatically retrieved keys.
|
||||
* Note: adding keys automatically is vulnerable to a man-in-the-middle attack, thus considered insecure and not recommended.
|
||||
* Adds ssh keys for specified host (which also can be an ip-address) to the ssh-file "known_hosts".
|
||||
* If parameter verifyKeys is true, the keys are checked against the live keys of the host and added only if valid.
|
||||
*/
|
||||
fun Prov.trustHost(host: String, fingerprintsOfKeysToBeAdded: Set<String>?) = taskWithResult {
|
||||
if (isHostKnown(host)) {
|
||||
return@taskWithResult ProvResult(true, out = "Host already known")
|
||||
}
|
||||
if (!checkFile(KNOWN_HOSTS_FILE)) {
|
||||
createDir(".ssh")
|
||||
createFile(KNOWN_HOSTS_FILE, null)
|
||||
}
|
||||
if (fingerprintsOfKeysToBeAdded == null) {
|
||||
// auto add keys
|
||||
cmd("ssh-keyscan $host >> $KNOWN_HOSTS_FILE")
|
||||
} else {
|
||||
// logic based on https://serverfault.com/questions/447028/non-interactive-git-clone-ssh-fingerprint-prompt
|
||||
val actualKeys = findSshKeys(host)
|
||||
if (actualKeys == null || actualKeys.size == 0) {
|
||||
return@taskWithResult ProvResult(false, out = "No valid keys found for host: $host")
|
||||
}
|
||||
val actualFingerprints = getFingerprintsForKeys(actualKeys)
|
||||
for (fingerprintToBeAdded in fingerprintsOfKeysToBeAdded) {
|
||||
var indexOfKeyFound = -1
|
||||
fun Prov.addKnownHost(knownHost: KnownHost, verifyKeys: Boolean = false) = task {
|
||||
val knownHostsFile = "~/.ssh/known_hosts"
|
||||
|
||||
// search for fingerprint in actual fingerprints
|
||||
for ((i, actualFingerprint) in actualFingerprints.withIndex()) {
|
||||
if (actualFingerprint.contains(fingerprintToBeAdded)) {
|
||||
indexOfKeyFound = i
|
||||
break
|
||||
if (!checkFile(knownHostsFile)) {
|
||||
createDir(".ssh")
|
||||
createFile(knownHostsFile, null)
|
||||
}
|
||||
}
|
||||
if (indexOfKeyFound == -1) {
|
||||
return@taskWithResult ProvResult(
|
||||
with(knownHost) {
|
||||
for (key in hostKeys) {
|
||||
if (!verifyKeys) {
|
||||
addTextToFile("\n$hostName $key\n", File(knownHostsFile))
|
||||
} else {
|
||||
val validKeys = findSshKeys(hostName, port)
|
||||
if (validKeys?.contains(key) == true) {
|
||||
val formattedHost = hostInKnownHostsFileFormat(hostName, port)
|
||||
addTextToFile("\n$formattedHost $key\n", File(knownHostsFile))
|
||||
} else {
|
||||
addResultToEval(
|
||||
ProvResult(
|
||||
false,
|
||||
err = "Fingerprint ($fingerprintToBeAdded) could not be found in actual fingerprints: $actualFingerprints"
|
||||
err = "The following key of host [$hostName] could not be verified successfully: " + key
|
||||
)
|
||||
)
|
||||
}
|
||||
cmd(echoCommandForText(actualKeys.get(indexOfKeyFound) + "\n") + " >> $KNOWN_HOSTS_FILE")
|
||||
}
|
||||
ProvResult(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of valid ssh keys for the given host (host can also be an ip address)
|
||||
* Returns a list of valid ssh keys for the given host (host can also be an ip address),
|
||||
* keys are returned (space-separated) as keytype and key, but WITHOUT the host name.*
|
||||
* If no port is specified, the keys for the default port (22) are returned.
|
||||
* If no keytype is specified, keys are returned for all keytypes.
|
||||
*/
|
||||
private fun Prov.findSshKeys(host: String): List<String>? {
|
||||
return cmd("ssh-keyscan $host 2>/dev/null").out?.split("\n")?.filter { x -> "" != x }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of fingerprints of the given sshKeys; the returning list has same size and order as the specified list of sshKeys
|
||||
*/
|
||||
private fun Prov.getFingerprintsForKeys(sshKeys: List<String>): List<String> {
|
||||
return sshKeys.map { x -> cmd("echo \"$x\" | ssh-keygen -lf -").out ?: "" }
|
||||
fun Prov.findSshKeys(host: String, port: Int? = null, keytype: String? = null): List<String>? {
|
||||
val portOption = port?.let { " -p $port " } ?: ""
|
||||
val keytypeOption = keytype?.let { " -t $keytype " } ?: ""
|
||||
val output = cmd("ssh-keyscan $portOption $keytypeOption $host 2>/dev/null").out?.trim()
|
||||
return output?.split("\n")?.filter { x -> "" != x }?.map { x -> x.substringAfter(" ") }
|
||||
}
|
||||
|
|
|
@ -6,24 +6,25 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.
|
|||
|
||||
|
||||
@Serializable
|
||||
abstract class SecretSource(protected val input: String) {
|
||||
abstract class SecretSource(protected val parameter: String) {
|
||||
abstract fun secret() : Secret
|
||||
abstract fun secretNullable() : Secret?
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
enum class SecretSourceType() {
|
||||
enum class SecretSourceType {
|
||||
|
||||
PLAIN, FILE, PROMPT, PASS, GOPASS;
|
||||
PLAIN, FILE, PROMPT, PASS, GOPASS, ENV;
|
||||
|
||||
fun secret(input: String) : Secret {
|
||||
fun secret(parameter: String) : Secret {
|
||||
return when (this) {
|
||||
PLAIN -> PlainSecretSource(input).secret()
|
||||
FILE -> FileSecretSource(input).secret()
|
||||
PLAIN -> PlainSecretSource(parameter).secret()
|
||||
FILE -> FileSecretSource(parameter).secret()
|
||||
PROMPT -> PromptSecretSource().secret()
|
||||
PASS -> PassSecretSource(input).secret()
|
||||
GOPASS -> GopassSecretSource(input).secret()
|
||||
PASS -> PassSecretSource(parameter).secret()
|
||||
GOPASS -> GopassSecretSource(parameter).secret()
|
||||
ENV -> EnvSecretSource(parameter).secret()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources
|
||||
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
||||
|
||||
|
||||
/**
|
||||
* Reads secret from a local environment variable
|
||||
*/
|
||||
class EnvSecretSource(varName: String) : SecretSource(varName) {
|
||||
override fun secret(): Secret {
|
||||
return secretNullable() ?: throw Exception("Failed to get secret from environment variable: $parameter")
|
||||
}
|
||||
override fun secretNullable(): Secret? {
|
||||
val secret = System.getenv(parameter)
|
||||
return if (secret == null) null else Secret(secret)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources
|
||||
|
||||
import org.domaindrivenarchitecture.provs.framework.core.ProgressType
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
||||
|
@ -11,12 +12,12 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
|||
class FileSecretSource(fqFileName: String) : SecretSource(fqFileName) {
|
||||
|
||||
override fun secret(): Secret {
|
||||
val p = Prov.newInstance(name = "FileSecretSource")
|
||||
return p.getSecret("cat " + input) ?: throw Exception("Failed to get secret.")
|
||||
val p = Prov.newInstance(name = "FileSecretSource", progressType = ProgressType.NONE)
|
||||
return p.getSecret("cat " + parameter) ?: throw Exception("Failed to get secret.")
|
||||
}
|
||||
|
||||
override fun secretNullable(): Secret? {
|
||||
val p = Prov.newInstance(name = "FileSecretSource")
|
||||
return p.getSecret("cat " + input)
|
||||
val p = Prov.newInstance(name = "FileSecretSource", progressType = ProgressType.NONE)
|
||||
return p.getSecret("cat " + parameter)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources
|
||||
|
||||
import org.domaindrivenarchitecture.provs.framework.core.ProgressType
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
||||
|
@ -10,10 +11,10 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
|||
*/
|
||||
class GopassSecretSource(path: String) : SecretSource(path) {
|
||||
override fun secret(): Secret {
|
||||
return secretNullable() ?: throw Exception("Failed to get \"$input\" secret from gopass.")
|
||||
return secretNullable() ?: throw Exception("Failed to get \"$parameter\" secret from gopass.")
|
||||
}
|
||||
override fun secretNullable(): Secret? {
|
||||
val p = Prov.newInstance(name = "GopassSecretSource for $input")
|
||||
return p.getSecret("gopass show -f $input", true)
|
||||
val p = Prov.newInstance(name = "GopassSecretSource for $parameter", progressType = ProgressType.NONE)
|
||||
return p.getSecret("gopass show -f $parameter", true)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources
|
||||
|
||||
import org.domaindrivenarchitecture.provs.framework.core.ProgressType
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
||||
|
@ -10,11 +11,11 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
|||
*/
|
||||
class PassSecretSource(path: String) : SecretSource(path) {
|
||||
override fun secret(): Secret {
|
||||
val p = Prov.newInstance(name = "PassSecretSource")
|
||||
return p.getSecret("pass " + input) ?: throw Exception("Failed to get secret.")
|
||||
val p = Prov.newInstance(name = "PassSecretSource", progressType = ProgressType.NONE)
|
||||
return p.getSecret("pass " + parameter) ?: throw Exception("Failed to get secret.")
|
||||
}
|
||||
override fun secretNullable(): Secret? {
|
||||
val p = Prov.newInstance(name = "PassSecretSource")
|
||||
return p.getSecret("pass " + input)
|
||||
val p = Prov.newInstance(name = "PassSecretSource", progressType = ProgressType.NONE)
|
||||
return p.getSecret("pass " + parameter)
|
||||
}
|
||||
}
|
|
@ -6,9 +6,9 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
|||
|
||||
class PlainSecretSource(plainSecret: String) : SecretSource(plainSecret) {
|
||||
override fun secret(): Secret {
|
||||
return Secret(input)
|
||||
return Secret(parameter)
|
||||
}
|
||||
override fun secretNullable(): Secret {
|
||||
return Secret(input)
|
||||
return Secret(parameter)
|
||||
}
|
||||
}
|
|
@ -47,7 +47,7 @@ class PasswordPanel : JPanel(FlowLayout()) {
|
|||
class PromptSecretSource(text: String = "Secret/Password") : SecretSource(text) {
|
||||
|
||||
override fun secret(): Secret {
|
||||
val password = PasswordPanel.requestPassword(input)
|
||||
val password = PasswordPanel.requestPassword(parameter)
|
||||
if (password == null) {
|
||||
throw IllegalArgumentException("Failed to retrieve secret from prompting.")
|
||||
} else {
|
||||
|
@ -56,7 +56,7 @@ class PromptSecretSource(text: String = "Secret/Password") : SecretSource(text)
|
|||
}
|
||||
|
||||
override fun secretNullable(): Secret? {
|
||||
val password = PasswordPanel.requestPassword(input)
|
||||
val password = PasswordPanel.requestPassword(parameter)
|
||||
|
||||
return if(password == null) {
|
||||
null
|
||||
|
|
|
@ -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.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.provisionGrafanaAgent
|
||||
import org.domaindrivenarchitecture.provs.server.infrastructure.*
|
||||
|
@ -11,6 +13,7 @@ import kotlin.system.exitProcess
|
|||
fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task {
|
||||
|
||||
val grafanaConfigResolved: GrafanaAgentConfigResolved? = findK8sGrafanaConfig(cli.configFileName)?.resolveSecret()
|
||||
val hcloudConfigResolved: HetznerCSIConfigResolved? = findHetznerCSIConfig(cli.configFileName)?.resolveSecret()
|
||||
|
||||
if (cli.onlyModules == null) {
|
||||
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 applicationFile = cli.applicationFileName?.let { DefaultApplicationFileRepository(cli.applicationFileName).getFile() }
|
||||
provisionK3s(k3sConfigReprovision, grafanaConfigResolved, applicationFile)
|
||||
provisionK3s(k3sConfigReprovision, grafanaConfigResolved, hcloudConfigResolved, applicationFile)
|
||||
} else {
|
||||
provisionGrafana(cli.onlyModules, grafanaConfigResolved)
|
||||
provisionHetznerCSI(cli.onlyModules, hcloudConfigResolved)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,6 +34,7 @@ fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task {
|
|||
fun Prov.provisionK3s(
|
||||
k3sConfig: K3sConfig,
|
||||
grafanaConfigResolved: GrafanaAgentConfigResolved? = null,
|
||||
hetznerCSIConfigResolved: HetznerCSIConfigResolved? = null,
|
||||
applicationFile: ApplicationFile? = null
|
||||
) = task {
|
||||
|
||||
|
@ -53,6 +58,10 @@ fun Prov.provisionK3s(
|
|||
provisionGrafanaAgent(grafanaConfigResolved)
|
||||
}
|
||||
|
||||
if (hetznerCSIConfigResolved != null) {
|
||||
provisionHetznerCSI(hetznerCSIConfigResolved)
|
||||
}
|
||||
|
||||
if (applicationFile != null) {
|
||||
provisionK3sApplication(applicationFile)
|
||||
}
|
||||
|
@ -60,6 +69,8 @@ fun Prov.provisionK3s(
|
|||
if (!k3sConfig.reprovision) {
|
||||
provisionServerCliConvenience()
|
||||
}
|
||||
|
||||
installK9s()
|
||||
}
|
||||
|
||||
private fun Prov.provisionGrafana(
|
||||
|
@ -75,3 +86,18 @@ private fun Prov.provisionGrafana(
|
|||
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
|
||||
|
||||
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.ProvResult
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFile
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFileFromResource
|
||||
|
||||
private const val resourcePath = "org/domaindrivenarchitecture/provs/desktop/infrastructure"
|
||||
|
@ -16,7 +15,8 @@ fun Prov.provisionServerCliConvenience() = task {
|
|||
fun Prov.provisionKubectlCompletionAndAlias(): ProvResult = task {
|
||||
cmd("kubectl completion bash | sudo tee /etc/bash_completion.d/kubectl > /dev/null")
|
||||
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 {
|
||||
|
|
|
@ -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()) }
|
|
@ -1,39 +0,0 @@
|
|||
package org.domaindrivenarchitecture.provs.server.domain
|
||||
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.framework.core.docker.provideContainer
|
||||
import org.domaindrivenarchitecture.provs.framework.core.echoCommandForTextWithNewlinesReplaced
|
||||
import org.domaindrivenarchitecture.provs.framework.core.repeatTaskUntilSuccess
|
||||
|
||||
|
||||
/**
|
||||
* Runs a k3s server and a k3s agent as containers.
|
||||
* Copies the kubeconfig from container to the default location: $HOME/.kube/config
|
||||
*/
|
||||
fun Prov.installK3sAsContainers(token: String = "12345678901234") = task {
|
||||
cmd("docker volume create k3s-server")
|
||||
provideContainer("k3s-server", "rancher/k3s", command = "server --cluster-init", options =
|
||||
"-d --privileged --tmpfs /run --tmpfs /var/run " +
|
||||
"-e K3S_TOKEN=$token -e K3S_KUBECONFIG_OUTPUT=./kubeconfig.yaml -e K3S_KUBECONFIG_MODE=666 " +
|
||||
"-v k3s-server:/var/lib/rancher/k3s:z -p 6443:6443 -p 80:80 -p 443:443 " +
|
||||
"--ulimit nproc=65535 --ulimit nofile=65535:65535")
|
||||
|
||||
// wait for config file
|
||||
cmd("export timeout=60; while [ ! -f /var/lib/docker/volumes/k3s-server/_data/server/kubeconfig.yaml ]; do if [ \"${'$'}timeout\" == 0 ]; then echo \"ERROR: Timeout while waiting for file.\"; break; fi; sleep 1; ((timeout--)); done")
|
||||
|
||||
sh("""
|
||||
mkdir -p ${'$'}HOME/.kube/
|
||||
cp /var/lib/docker/volumes/k3s-server/_data/server/kubeconfig.yaml ${'$'}HOME/.kube/config
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Apply a config to kubernetes.
|
||||
* Prerequisite: Kubectl has to be installed
|
||||
*/
|
||||
fun Prov.applyK8sConfig(configAsYaml: String, kubectlCommand: String = "kubectl") = task {
|
||||
repeatTaskUntilSuccess(6, 10) {
|
||||
cmd(echoCommandForTextWithNewlinesReplaced(configAsYaml) + " | $kubectlCommand apply -f -")
|
||||
}
|
||||
}
|
|
@ -10,7 +10,9 @@ import java.io.File
|
|||
|
||||
// ----------------------------------- versions --------------------------------
|
||||
|
||||
const val K3S_VERSION = "v1.23.6+k3s1"
|
||||
// when updating this version, it is recommended to update also file k3s-install.sh as well as traefik.yaml in this repo
|
||||
// (both files in: src/main/resources/org/domaindrivenarchitecture/provs/server/infrastructure/k3s/)
|
||||
const val K3S_VERSION = "v1.29.1+k3s2"
|
||||
|
||||
// ----------------------------------- directories --------------------------------
|
||||
const val k3sManualManifestsDir = "/etc/rancher/k3s/manifests/"
|
||||
|
@ -31,12 +33,12 @@ private val k3sMiddleWareHttpsRedirect = File(k3sManualManifestsDir, "middleware
|
|||
private val certManagerDeployment = File(k3sManualManifestsDir, "cert-manager.yaml")
|
||||
|
||||
private val certManagerIssuer = File(k3sManualManifestsDir, "le-issuer.yaml")
|
||||
private val k3sEcho = File(k3sManualManifestsDir, "echo.yaml")
|
||||
private val k3sEchoWithTls = File(k3sManualManifestsDir, "echo-tls.yaml")
|
||||
private val k3sEchoNoTls = File(k3sManualManifestsDir, "echo-no-tls.yaml")
|
||||
private val selfSignedCertificate = File(k3sManualManifestsDir, "selfsigned-certificate.yaml")
|
||||
|
||||
private val localPathProvisionerConfig = File(k3sManualManifestsDir, "local-path-provisioner-config.yaml")
|
||||
|
||||
|
||||
// ----------------------------------- public functions --------------------------------
|
||||
|
||||
fun Prov.testConfigExists(): Boolean {
|
||||
|
@ -49,7 +51,11 @@ fun Prov.deprovisionK3sInfra() = task {
|
|||
deleteFile(certManagerDeployment.path, sudo = true)
|
||||
deleteFile(certManagerIssuer.path, sudo = true)
|
||||
deleteFile(k3sKubeConfig.path, sudo = true)
|
||||
cmd("k3s-uninstall.sh")
|
||||
|
||||
val k3sUninstallScript = "k3s-uninstall.sh"
|
||||
if (chk("which $k3sUninstallScript")) {
|
||||
cmd(k3sUninstallScript)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -95,7 +101,7 @@ fun Prov.installK3s(k3sConfig: K3sConfig): ProvResult {
|
|||
// metallb
|
||||
applyK3sFileFromResource(File(k3sManualManifestsDir, "metallb-0.13.7-native-manifest.yaml"))
|
||||
|
||||
repeatTaskUntilSuccess(6, 10) {
|
||||
repeatTaskUntilSuccess(10, 10) {
|
||||
applyK3sFileFromResourceTemplate(
|
||||
File(k3sManualManifestsDir, "metallb-config.yaml"),
|
||||
k3sConfigMap,
|
||||
|
@ -117,8 +123,9 @@ fun Prov.installK3s(k3sConfig: K3sConfig): ProvResult {
|
|||
applyK3sFileFromResource(k3sMiddleWareHttpsRedirect)
|
||||
}
|
||||
|
||||
// other
|
||||
applyK3sFileFromResource(localPathProvisionerConfig)
|
||||
// TODO: jem 2022-11-25: Why do we need sudo here??
|
||||
|
||||
cmd("kubectl set env deployment -n kube-system local-path-provisioner DEPLOY_DATE=\"$(date)\"", sudo = true)
|
||||
|
||||
cmd("ln -sf $k3sKubeConfig " + k8sCredentialsDir + "admin.conf", sudo = true)
|
||||
|
@ -144,7 +151,8 @@ fun Prov.provisionK3sCertManager(certmanager: Certmanager) = task {
|
|||
}
|
||||
}
|
||||
|
||||
fun Prov.provisionK3sEcho(fqdn: String, endpoint: CertmanagerEndpoint? = null) = task {
|
||||
fun Prov.provisionK3sEcho(fqdn: String, endpoint: CertmanagerEndpoint? = null, withTls: Boolean = false) = task {
|
||||
if (withTls) {
|
||||
val endpointName = endpoint?.name?.lowercase()
|
||||
|
||||
val issuer = if (endpointName == null) {
|
||||
|
@ -153,8 +161,10 @@ fun Prov.provisionK3sEcho(fqdn: String, endpoint: CertmanagerEndpoint? = null) =
|
|||
} else {
|
||||
endpointName
|
||||
}
|
||||
|
||||
applyK3sFileFromResourceTemplate(k3sEcho, mapOf("fqdn" to fqdn, "issuer_name" to issuer))
|
||||
applyK3sFileFromResourceTemplate(k3sEchoWithTls, mapOf("fqdn" to fqdn, "issuer_name" to issuer))
|
||||
} else {
|
||||
applyK3sFileFromResource(k3sEchoNoTls)
|
||||
}
|
||||
}
|
||||
|
||||
fun Prov.provisionK3sApplication(applicationFile: ApplicationFile) = task {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -1,10 +1,5 @@
|
|||
package org.domaindrivenarchitecture.provs.syspec.infrastructure
|
||||
|
||||
import aws.sdk.kotlin.services.s3.S3Client
|
||||
import aws.sdk.kotlin.services.s3.model.ListObjectsRequest
|
||||
import aws.sdk.kotlin.services.s3.model.ListObjectsResponse
|
||||
import aws.smithy.kotlin.runtime.time.Instant
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.framework.core.ProvResult
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.checkDir
|
||||
|
@ -13,7 +8,6 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.isPackag
|
|||
import org.domaindrivenarchitecture.provs.syspec.domain.*
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
@ -29,7 +23,6 @@ fun Prov.verifySpecConfig(conf: SyspecConfig) = task {
|
|||
conf.netcat?.let { task("NetcatSpecs") { for (spec in conf.netcat) verify(spec) } }
|
||||
conf.socket?.let { task("SocketSpecs") { for (spec in conf.socket) verify(spec) } }
|
||||
conf.certificate?.let { task("CertificateFileSpecs") { for (spec in conf.certificate) verify(spec) } }
|
||||
conf.s3?.let { task("CertificateFileSpecs") { for (spec in conf.s3) verify(spec) } }
|
||||
}
|
||||
|
||||
// ------------------------------- verification functions for individual specs --------------------------------
|
||||
|
@ -112,27 +105,6 @@ fun Prov.verify(cert: CertificateFileSpec) {
|
|||
}
|
||||
}
|
||||
|
||||
fun Prov.verify(s3ObjectSpec: S3ObjectSpec) {
|
||||
val (bucket, prefix, maxAge) = s3ObjectSpec
|
||||
val expectedAge = Duration.ofHours(s3ObjectSpec.age)
|
||||
|
||||
val latestObject = getS3Objects(bucket, prefix).contents?.maxByOrNull { it.lastModified ?: Instant.fromEpochSeconds(0) }
|
||||
|
||||
if (latestObject == null) {
|
||||
verify(false, "Could not retrieve an s3 object with prefix $prefix")
|
||||
} else {
|
||||
// convert to java.time.Instant for easier comparison
|
||||
val lastModified = java.time.Instant.ofEpochSecond(latestObject.lastModified?.epochSeconds ?: 0)
|
||||
val actualAge = Duration.between(lastModified, java.time.Instant.now())
|
||||
|
||||
verify(
|
||||
actualAge <= expectedAge,
|
||||
"Age is ${actualAge.toHours()} h (expected: <= $maxAge) for latest file with prefix \"$prefix\" " +
|
||||
"--- modified date: $lastModified - size: ${(latestObject.size)} B - key: ${latestObject.key}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -------------------------- helper functions ---------------------------------
|
||||
|
||||
|
@ -215,14 +187,3 @@ private fun Prov.verifyCertExpiration(enddate: String?, certName: String, expira
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getS3Objects(bucketName: String, prefixIn: String): ListObjectsResponse {
|
||||
|
||||
val request = ListObjectsRequest { bucket = bucketName; prefix = prefixIn }
|
||||
|
||||
return runBlocking {
|
||||
S3Client { region = "eu-central-1" }.use { s3 ->
|
||||
s3.listObjects(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ function usage() {
|
|||
|
||||
function main() {
|
||||
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}
|
||||
kubectl config use-context ${cluster_name}
|
||||
|
|
|
@ -4,8 +4,9 @@ set -o noglob
|
|||
|
||||
function main() {
|
||||
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
|
||||
|
|
|
@ -4,8 +4,9 @@ set -o noglob
|
|||
|
||||
function main() {
|
||||
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
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,40 @@
|
|||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: echo-ingress
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- pathType: Exact
|
||||
path: /echo/ # traefik echo pod needs the trailing slash, otherwise it'll return bad request
|
||||
backend:
|
||||
service:
|
||||
name: echo-service
|
||||
port:
|
||||
number: 80
|
||||
|
||||
---
|
||||
|
||||
kind: Pod
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: echo-app
|
||||
labels:
|
||||
app: echo
|
||||
spec:
|
||||
containers:
|
||||
- name: echo-app
|
||||
image: traefik/whoami
|
||||
---
|
||||
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: echo-service
|
||||
spec:
|
||||
selector:
|
||||
app: echo
|
||||
ports:
|
||||
- port: 80 # Default port for image
|
|
@ -3,15 +3,15 @@ apiVersion: networking.k8s.io/v1
|
|||
metadata:
|
||||
name: echo-ingress
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "traefik"
|
||||
cert-manager.io/cluster-issuer: ${issuer_name}
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: ${fqdn}
|
||||
http:
|
||||
paths:
|
||||
- pathType: Prefix
|
||||
path: /echo
|
||||
- pathType: Exact
|
||||
path: /echo/ # traefik echo pod needs the trailing slash, otherwise it'll return bad request
|
||||
backend:
|
||||
service:
|
||||
name: echo-service
|
|
@ -1,4 +1,6 @@
|
|||
#!/bin/sh
|
||||
# File taken from https://github.com/k3s-io/k3s/blob/master/install.sh
|
||||
|
||||
set -e
|
||||
set -o noglob
|
||||
|
||||
|
@ -18,7 +20,7 @@ set -o noglob
|
|||
# Environment variables which begin with K3S_ will be preserved for the
|
||||
# systemd service to use. Setting K3S_URL without explicitly setting
|
||||
# a systemd exec command will default the command to "agent", and we
|
||||
# enforce that K3S_TOKEN or K3S_CLUSTER_SECRET is also set.
|
||||
# enforce that K3S_TOKEN is also set.
|
||||
#
|
||||
# - INSTALL_K3S_SKIP_DOWNLOAD
|
||||
# If set to true will not download k3s hash or binary.
|
||||
|
@ -44,6 +46,10 @@ set -o noglob
|
|||
# Commit of k3s to download from temporary cloud storage.
|
||||
# * (for developer & QA use)
|
||||
#
|
||||
# - INSTALL_K3S_PR
|
||||
# PR build of k3s to download from Github Artifacts.
|
||||
# * (for developer & QA use)
|
||||
#
|
||||
# - INSTALL_K3S_BIN_DIR
|
||||
# Directory to install k3s binary, links, and uninstall script to, or use
|
||||
# /usr/local/bin as the default
|
||||
|
@ -92,7 +98,8 @@ set -o noglob
|
|||
# Defaults to 'stable'.
|
||||
|
||||
GITHUB_URL=https://github.com/k3s-io/k3s/releases
|
||||
STORAGE_URL=https://storage.googleapis.com/k3s-ci-builds
|
||||
GITHUB_PR_URL=""
|
||||
STORAGE_URL=https://k3s-ci-builds.s3.amazonaws.com
|
||||
DOWNLOADER=
|
||||
|
||||
# --- helper functions for logs ---
|
||||
|
@ -170,8 +177,8 @@ setup_env() {
|
|||
if [ -z "${K3S_URL}" ]; then
|
||||
CMD_K3S=server
|
||||
else
|
||||
if [ -z "${K3S_TOKEN}" ] && [ -z "${K3S_TOKEN_FILE}" ] && [ -z "${K3S_CLUSTER_SECRET}" ]; then
|
||||
fatal "Defaulted k3s exec command to 'agent' because K3S_URL is defined, but K3S_TOKEN, K3S_TOKEN_FILE or K3S_CLUSTER_SECRET is not defined."
|
||||
if [ -z "${K3S_TOKEN}" ] && [ -z "${K3S_TOKEN_FILE}" ]; then
|
||||
fatal "Defaulted k3s exec command to 'agent' because K3S_URL is defined, but K3S_TOKEN or K3S_TOKEN_FILE is not defined."
|
||||
fi
|
||||
CMD_K3S=agent
|
||||
fi
|
||||
|
@ -217,11 +224,7 @@ setup_env() {
|
|||
if [ -n "${INSTALL_K3S_TYPE}" ]; then
|
||||
SYSTEMD_TYPE=${INSTALL_K3S_TYPE}
|
||||
else
|
||||
if [ "${CMD_K3S}" = server ]; then
|
||||
SYSTEMD_TYPE=notify
|
||||
else
|
||||
SYSTEMD_TYPE=exec
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- use binary install directory if defined or create default ---
|
||||
|
@ -273,8 +276,14 @@ setup_env() {
|
|||
}
|
||||
|
||||
# --- check if skip download environment variable set ---
|
||||
can_skip_download() {
|
||||
if [ "${INSTALL_K3S_SKIP_DOWNLOAD}" != true ]; then
|
||||
can_skip_download_binary() {
|
||||
if [ "${INSTALL_K3S_SKIP_DOWNLOAD}" != true ] && [ "${INSTALL_K3S_SKIP_DOWNLOAD}" != binary ]; then
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
can_skip_download_selinux() {
|
||||
if [ "${INSTALL_K3S_SKIP_DOWNLOAD}" != true ] && [ "${INSTALL_K3S_SKIP_DOWNLOAD}" != selinux ]; then
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
@ -304,6 +313,10 @@ setup_verify_arch() {
|
|||
ARCH=arm64
|
||||
SUFFIX=-${ARCH}
|
||||
;;
|
||||
s390x)
|
||||
ARCH=s390x
|
||||
SUFFIX=-${ARCH}
|
||||
;;
|
||||
aarch64)
|
||||
ARCH=arm64
|
||||
SUFFIX=-${ARCH}
|
||||
|
@ -331,6 +344,7 @@ verify_downloader() {
|
|||
setup_tmp() {
|
||||
TMP_DIR=$(mktemp -d -t k3s-install.XXXXXXXXXX)
|
||||
TMP_HASH=${TMP_DIR}/k3s.hash
|
||||
TMP_ZIP=${TMP_DIR}/k3s.zip
|
||||
TMP_BIN=${TMP_DIR}/k3s.bin
|
||||
cleanup() {
|
||||
code=$?
|
||||
|
@ -344,7 +358,10 @@ setup_tmp() {
|
|||
|
||||
# --- use desired k3s version if defined or find version from channel ---
|
||||
get_release_version() {
|
||||
if [ -n "${INSTALL_K3S_COMMIT}" ]; then
|
||||
if [ -n "${INSTALL_K3S_PR}" ]; then
|
||||
VERSION_K3S="PR ${INSTALL_K3S_PR}"
|
||||
get_pr_artifact_url
|
||||
elif [ -n "${INSTALL_K3S_COMMIT}" ]; then
|
||||
VERSION_K3S="commit ${INSTALL_K3S_COMMIT}"
|
||||
elif [ -n "${INSTALL_K3S_VERSION}" ]; then
|
||||
VERSION_K3S=${INSTALL_K3S_VERSION}
|
||||
|
@ -366,10 +383,49 @@ get_release_version() {
|
|||
info "Using ${VERSION_K3S} as release"
|
||||
}
|
||||
|
||||
# --- get k3s-selinux version ---
|
||||
get_k3s_selinux_version() {
|
||||
available_version="k3s-selinux-1.2-2.${rpm_target}.noarch.rpm"
|
||||
info "Finding available k3s-selinux versions"
|
||||
|
||||
# run verify_downloader in case it binary installation was skipped
|
||||
verify_downloader curl || verify_downloader wget || fatal 'Can not find curl or wget for downloading files'
|
||||
|
||||
case $DOWNLOADER in
|
||||
curl)
|
||||
DOWNLOADER_OPTS="-s"
|
||||
;;
|
||||
wget)
|
||||
DOWNLOADER_OPTS="-q -O -"
|
||||
;;
|
||||
*)
|
||||
fatal "Incorrect downloader executable '$DOWNLOADER'"
|
||||
;;
|
||||
esac
|
||||
for i in {1..3}; do
|
||||
set +e
|
||||
if [ "${rpm_channel}" = "testing" ]; then
|
||||
version=$(timeout 5 ${DOWNLOADER} ${DOWNLOADER_OPTS} https://api.github.com/repos/k3s-io/k3s-selinux/releases | grep browser_download_url | awk '{ print $2 }' | grep -oE "[^\/]+${rpm_target}\.noarch\.rpm" | head -n 1)
|
||||
else
|
||||
version=$(timeout 5 ${DOWNLOADER} ${DOWNLOADER_OPTS} https://api.github.com/repos/k3s-io/k3s-selinux/releases/latest | grep browser_download_url | awk '{ print $2 }' | grep -oE "[^\/]+${rpm_target}\.noarch\.rpm")
|
||||
fi
|
||||
set -e
|
||||
if [ "${version}" != "" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if [ "${version}" == "" ]; then
|
||||
warn "Failed to get available versions of k3s-selinux..defaulting to ${available_version}"
|
||||
return
|
||||
fi
|
||||
available_version=${version}
|
||||
}
|
||||
|
||||
# --- download from github url ---
|
||||
download() {
|
||||
[ $# -eq 2 ] || fatal 'download needs exactly 2 arguments'
|
||||
|
||||
set +e
|
||||
case $DOWNLOADER in
|
||||
curl)
|
||||
curl -o $1 -sfL $2
|
||||
|
@ -384,10 +440,17 @@ download() {
|
|||
|
||||
# Abort if download command failed
|
||||
[ $? -eq 0 ] || fatal 'Download failed'
|
||||
set -e
|
||||
}
|
||||
|
||||
# --- download hash from github url ---
|
||||
download_hash() {
|
||||
if [ -n "${INSTALL_K3S_PR}" ]; then
|
||||
info "Downloading hash ${GITHUB_PR_URL}"
|
||||
curl -o ${TMP_ZIP} -H "Authorization: Bearer $GITHUB_TOKEN" -L ${GITHUB_PR_URL}
|
||||
unzip -p ${TMP_ZIP} k3s.sha256sum > ${TMP_HASH}
|
||||
sed -i 's/dist\/artifacts\/k3s/k3s/g' ${TMP_HASH}
|
||||
else
|
||||
if [ -n "${INSTALL_K3S_COMMIT}" ]; then
|
||||
HASH_URL=${STORAGE_URL}/k3s${SUFFIX}-${INSTALL_K3S_COMMIT}.sha256sum
|
||||
else
|
||||
|
@ -395,6 +458,7 @@ download_hash() {
|
|||
fi
|
||||
info "Downloading hash ${HASH_URL}"
|
||||
download ${TMP_HASH} ${HASH_URL}
|
||||
fi
|
||||
HASH_EXPECTED=$(grep " k3s${SUFFIX}$" ${TMP_HASH})
|
||||
HASH_EXPECTED=${HASH_EXPECTED%%[[:blank:]]*}
|
||||
}
|
||||
|
@ -411,9 +475,48 @@ installed_hash_matches() {
|
|||
return 1
|
||||
}
|
||||
|
||||
# Use the GitHub API to identify the artifact associated with a given PR
|
||||
get_pr_artifact_url() {
|
||||
GITHUB_API_URL=https://api.github.com/repos/k3s-io/k3s
|
||||
|
||||
# Check if jq is installed
|
||||
if ! [ -x "$(command -v jq)" ]; then
|
||||
echo "jq is required to use INSTALL_K3S_PR. Please install jq and try again"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "${GITHUB_TOKEN}" ]; then
|
||||
fatal "Installing PR builds requires GITHUB_TOKEN with k3s-io/k3s repo authorization"
|
||||
fi
|
||||
|
||||
# GET request to the GitHub API to retrieve the latest commit SHA from the pull request
|
||||
COMMIT_ID=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" "$GITHUB_API_URL/pulls/$INSTALL_K3S_PR" | jq -r '.head.sha')
|
||||
|
||||
# GET request to the GitHub API to retrieve the Build workflow associated with the commit
|
||||
wf_raw=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" "$GITHUB_API_URL/commits/$COMMIT_ID/check-runs")
|
||||
build_workflow=$(printf "%s" "$wf_raw" | jq -r '.check_runs[] | select(.name == "build / Build")')
|
||||
|
||||
# Extract the Run ID from the build workflow and lookup artifacts associated with the run
|
||||
RUN_ID=$(echo "$build_workflow" | jq -r ' .details_url' | awk -F'/' '{print $(NF-2)}')
|
||||
|
||||
# Extract the artifat ID for the "k3s" artifact
|
||||
artifacts=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" "$GITHUB_API_URL/actions/runs/$RUN_ID/artifacts")
|
||||
artifacts_url=$(echo "$artifacts" | jq -r '.artifacts[] | select(.name == "k3s") | .archive_download_url')
|
||||
GITHUB_PR_URL=$artifacts_url
|
||||
}
|
||||
|
||||
# --- download binary from github url ---
|
||||
download_binary() {
|
||||
if [ -n "${INSTALL_K3S_COMMIT}" ]; then
|
||||
if [ -n "${INSTALL_K3S_PR}" ]; then
|
||||
# Since Binary and Hash are zipped together, check if TMP_ZIP already exists
|
||||
if ! [ -f ${TMP_ZIP} ]; then
|
||||
info "Downloading K3s artifact ${GITHUB_PR_URL}"
|
||||
curl -o ${TMP_ZIP} -H "Authorization: Bearer $GITHUB_TOKEN" -L ${GITHUB_PR_URL}
|
||||
fi
|
||||
# extract k3s binary from zip
|
||||
unzip -p ${TMP_ZIP} k3s > ${TMP_BIN}
|
||||
return
|
||||
elif [ -n "${INSTALL_K3S_COMMIT}" ]; then
|
||||
BIN_URL=${STORAGE_URL}/k3s${SUFFIX}-${INSTALL_K3S_COMMIT}
|
||||
else
|
||||
BIN_URL=${GITHUB_URL}/download/${VERSION_K3S}/k3s${SUFFIX}
|
||||
|
@ -460,18 +563,35 @@ setup_selinux() {
|
|||
fi
|
||||
|
||||
[ -r /etc/os-release ] && . /etc/os-release
|
||||
if [ "${ID_LIKE%%[ ]*}" = "suse" ]; then
|
||||
if [ `expr "${ID_LIKE}" : ".*suse.*"` != 0 ]; then
|
||||
rpm_target=sle
|
||||
rpm_site_infix=microos
|
||||
package_installer=zypper
|
||||
if [ "${ID_LIKE:-}" = suse ] && ( [ "${VARIANT_ID:-}" = sle-micro ] || [ "${ID:-}" = sle-micro ] ); then
|
||||
rpm_target=sle
|
||||
rpm_site_infix=slemicro
|
||||
package_installer=zypper
|
||||
fi
|
||||
elif [ "${ID_LIKE:-}" = coreos ] || [ "${VARIANT_ID:-}" = coreos ]; then
|
||||
rpm_target=coreos
|
||||
rpm_site_infix=coreos
|
||||
package_installer=rpm-ostree
|
||||
elif [ "${VERSION_ID%%.*}" = "7" ]; then
|
||||
rpm_target=el7
|
||||
rpm_site_infix=centos/7
|
||||
package_installer=yum
|
||||
else
|
||||
elif [ "${VERSION_ID%%.*}" = "8" ] || [ "${VERSION_ID%%.*}" -gt "36" ]; then
|
||||
rpm_target=el8
|
||||
rpm_site_infix=centos/8
|
||||
package_installer=yum
|
||||
else
|
||||
rpm_target=el9
|
||||
rpm_site_infix=centos/9
|
||||
package_installer=yum
|
||||
fi
|
||||
|
||||
if [ "${package_installer}" = "rpm-ostree" ] && [ -x /bin/yum ]; then
|
||||
package_installer=yum
|
||||
fi
|
||||
|
||||
if [ "${package_installer}" = "yum" ] && [ -x /usr/bin/dnf ]; then
|
||||
|
@ -480,15 +600,17 @@ setup_selinux() {
|
|||
|
||||
policy_hint="please install:
|
||||
${package_installer} install -y container-selinux
|
||||
${package_installer} install -y https://${rpm_site}/k3s/${rpm_channel}/common/${rpm_site_infix}/noarch/k3s-selinux-0.4-1.${rpm_target}.noarch.rpm
|
||||
${package_installer} install -y https://${rpm_site}/k3s/${rpm_channel}/common/${rpm_site_infix}/noarch/${available_version}
|
||||
"
|
||||
|
||||
if [ "$INSTALL_K3S_SKIP_SELINUX_RPM" = true ] || can_skip_download || [ ! -d /usr/share/selinux ]; then
|
||||
if [ "$INSTALL_K3S_SKIP_SELINUX_RPM" = true ] || can_skip_download_selinux || [ ! -d /usr/share/selinux ]; then
|
||||
info "Skipping installation of SELinux RPM"
|
||||
elif [ "${ID_LIKE:-}" != coreos ] && [ "${VARIANT_ID:-}" != coreos ]; then
|
||||
install_selinux_rpm ${rpm_site} ${rpm_channel} ${rpm_target} ${rpm_site_infix}
|
||||
return
|
||||
fi
|
||||
|
||||
get_k3s_selinux_version
|
||||
install_selinux_rpm ${rpm_site} ${rpm_channel} ${rpm_target} ${rpm_site_infix}
|
||||
|
||||
policy_error=fatal
|
||||
if [ "$INSTALL_K3S_SELINUX_WARN" = true ] || [ "${ID_LIKE:-}" = coreos ] || [ "${VARIANT_ID:-}" = coreos ]; then
|
||||
policy_error=warn
|
||||
|
@ -499,7 +621,7 @@ setup_selinux() {
|
|||
$policy_error "Failed to apply container_runtime_exec_t to ${BIN_DIR}/k3s, ${policy_hint}"
|
||||
fi
|
||||
elif [ ! -f /usr/share/selinux/packages/k3s.pp ]; then
|
||||
if [ -x /usr/sbin/transactional-update ]; then
|
||||
if [ -x /usr/sbin/transactional-update ] || [ "${ID_LIKE:-}" = coreos ] || [ "${VARIANT_ID:-}" = coreos ]; then
|
||||
warn "Please reboot your machine to activate the changes and avoid data loss."
|
||||
else
|
||||
$policy_error "Failed to find the k3s-selinux policy, ${policy_hint}"
|
||||
|
@ -508,7 +630,7 @@ setup_selinux() {
|
|||
}
|
||||
|
||||
install_selinux_rpm() {
|
||||
if [ -r /etc/redhat-release ] || [ -r /etc/centos-release ] || [ -r /etc/oracle-release ] || [ "${ID_LIKE%%[ ]*}" = "suse" ]; then
|
||||
if [ -r /etc/redhat-release ] || [ -r /etc/centos-release ] || [ -r /etc/oracle-release ] || [ -r /etc/fedora-release ] || [ "${ID_LIKE%%[ ]*}" = "suse" ]; then
|
||||
repodir=/etc/yum.repos.d
|
||||
if [ -d /etc/zypp/repos.d ]; then
|
||||
repodir=/etc/zypp/repos.d
|
||||
|
@ -533,9 +655,17 @@ EOF
|
|||
sle)
|
||||
rpm_installer="zypper --gpg-auto-import-keys"
|
||||
if [ "${TRANSACTIONAL_UPDATE=false}" != "true" ] && [ -x /usr/sbin/transactional-update ]; then
|
||||
transactional_update_run="transactional-update --no-selfupdate -d run"
|
||||
rpm_installer="transactional-update --no-selfupdate -d run ${rpm_installer}"
|
||||
: "${INSTALL_K3S_SKIP_START:=true}"
|
||||
fi
|
||||
# create the /var/lib/rpm-state in SLE systems to fix the prein selinux macro
|
||||
${transactional_update_run} mkdir -p /var/lib/rpm-state
|
||||
;;
|
||||
coreos)
|
||||
rpm_installer="rpm-ostree --idempotent"
|
||||
# rpm_install_extra_args="--apply-live"
|
||||
: "${INSTALL_K3S_SKIP_START:=true}"
|
||||
;;
|
||||
*)
|
||||
rpm_installer="yum"
|
||||
|
@ -543,6 +673,15 @@ EOF
|
|||
esac
|
||||
if [ "${rpm_installer}" = "yum" ] && [ -x /usr/bin/dnf ]; then
|
||||
rpm_installer=dnf
|
||||
fi
|
||||
if rpm -q --quiet k3s-selinux; then
|
||||
# remove k3s-selinux module before upgrade to allow container-selinux to upgrade safely
|
||||
if check_available_upgrades container-selinux ${3} && check_available_upgrades k3s-selinux ${3}; then
|
||||
MODULE_PRIORITY=$($SUDO semodule --list=full | grep k3s | cut -f1 -d" ")
|
||||
if [ -n "${MODULE_PRIORITY}" ]; then
|
||||
$SUDO semodule -X $MODULE_PRIORITY -r k3s || true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
# shellcheck disable=SC2086
|
||||
$SUDO ${rpm_installer} install -y "k3s-selinux"
|
||||
|
@ -550,9 +689,28 @@ EOF
|
|||
return
|
||||
}
|
||||
|
||||
check_available_upgrades() {
|
||||
set +e
|
||||
case ${2} in
|
||||
sle)
|
||||
available_upgrades=$($SUDO zypper -q -t -s 11 se -s -u --type package $1 | tail -n 1 | grep -v "No matching" | awk '{print $3}')
|
||||
;;
|
||||
coreos)
|
||||
# currently rpm-ostree does not support search functionality https://github.com/coreos/rpm-ostree/issues/1877
|
||||
;;
|
||||
*)
|
||||
available_upgrades=$($SUDO yum -q --refresh list $1 --upgrades | tail -n 1 | awk '{print $2}')
|
||||
;;
|
||||
esac
|
||||
set -e
|
||||
if [ -n "${available_upgrades}" ]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
# --- download and verify k3s ---
|
||||
download_and_verify() {
|
||||
if can_skip_download; then
|
||||
if can_skip_download_binary; then
|
||||
info 'Skipping k3s download and verify'
|
||||
verify_k3s_is_executable
|
||||
return
|
||||
|
@ -640,6 +798,27 @@ killtree() {
|
|||
) 2>/dev/null
|
||||
}
|
||||
|
||||
remove_interfaces() {
|
||||
# Delete network interface(s) that match 'master cni0'
|
||||
ip link show 2>/dev/null | grep 'master cni0' | while read ignore iface ignore; do
|
||||
iface=${iface%%@*}
|
||||
[ -z "$iface" ] || ip link delete $iface
|
||||
done
|
||||
|
||||
# Delete cni related interfaces
|
||||
ip link delete cni0
|
||||
ip link delete flannel.1
|
||||
ip link delete flannel-v6.1
|
||||
ip link delete kube-ipvs0
|
||||
ip link delete flannel-wg
|
||||
ip link delete flannel-wg-v6
|
||||
|
||||
# Restart tailscale
|
||||
if [ -n "$(command -v tailscale)" ]; then
|
||||
tailscale set --advertise-routes=
|
||||
fi
|
||||
}
|
||||
|
||||
getshims() {
|
||||
ps -e -o pid= -o args= | sed -e 's/^ *//; s/\s\s*/\t/;' | grep -w 'k3s/data/[^/]*/bin/containerd-shim' | cut -f1
|
||||
}
|
||||
|
@ -650,7 +829,7 @@ do_unmount_and_remove() {
|
|||
set +x
|
||||
while read -r _ path _; do
|
||||
case "$path" in $1*) echo "$path" ;; esac
|
||||
done < /proc/self/mounts | sort -r | xargs -r -t -n 1 sh -c 'umount "$0" && rm -rf "$0"'
|
||||
done < /proc/self/mounts | sort -r | xargs -r -t -n 1 sh -c 'umount -f "$0" && rm -rf "$0"'
|
||||
set -x
|
||||
}
|
||||
|
||||
|
@ -663,17 +842,11 @@ do_unmount_and_remove '/run/netns/cni-'
|
|||
# Remove CNI namespaces
|
||||
ip netns show 2>/dev/null | grep cni- | xargs -r -t -n 1 ip netns delete
|
||||
|
||||
# Delete network interface(s) that match 'master cni0'
|
||||
ip link show 2>/dev/null | grep 'master cni0' | while read ignore iface ignore; do
|
||||
iface=${iface%%@*}
|
||||
[ -z "$iface" ] || ip link delete $iface
|
||||
done
|
||||
ip link delete cni0
|
||||
ip link delete flannel.1
|
||||
ip link delete flannel-v6.1
|
||||
remove_interfaces
|
||||
|
||||
rm -rf /var/lib/cni/
|
||||
iptables-save | grep -v KUBE- | grep -v CNI- | iptables-restore
|
||||
ip6tables-save | grep -v KUBE- | grep -v CNI- | ip6tables-restore
|
||||
iptables-save | grep -v KUBE- | grep -v CNI- | grep -iv flannel | iptables-restore
|
||||
ip6tables-save | grep -v KUBE- | grep -v CNI- | grep -iv flannel | ip6tables-restore
|
||||
EOF
|
||||
$SUDO chmod 755 ${KILLALL_K3S_SH}
|
||||
$SUDO chown root:root ${KILLALL_K3S_SH}
|
||||
|
@ -729,6 +902,9 @@ rm -f ${KILLALL_K3S_SH}
|
|||
if type yum >/dev/null 2>&1; then
|
||||
yum remove -y k3s-selinux
|
||||
rm -f /etc/yum.repos.d/rancher-k3s-common*.repo
|
||||
elif type rpm-ostree >/dev/null 2>&1; then
|
||||
rpm-ostree uninstall k3s-selinux
|
||||
rm -f /etc/yum.repos.d/rancher-k3s-common*.repo
|
||||
elif type zypper >/dev/null 2>&1; then
|
||||
uninstall_cmd="zypper remove -y k3s-selinux"
|
||||
if [ "\${TRANSACTIONAL_UPDATE=false}" != "true" ] && [ -x /usr/sbin/transactional-update ]; then
|
||||
|
@ -787,7 +963,7 @@ TasksMax=infinity
|
|||
TimeoutStartSec=0
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
ExecStartPre=/bin/sh -xc '! /usr/bin/systemctl is-enabled --quiet nm-cloud-setup.service'
|
||||
ExecStartPre=/bin/sh -xc '! /usr/bin/systemctl is-enabled --quiet nm-cloud-setup.service 2>/dev/null'
|
||||
ExecStartPre=-/sbin/modprobe br_netfilter
|
||||
ExecStartPre=-/sbin/modprobe overlay
|
||||
ExecStart=${BIN_DIR}/k3s \\
|
||||
|
@ -827,8 +1003,8 @@ respawn_delay=5
|
|||
respawn_max=0
|
||||
|
||||
set -o allexport
|
||||
if [ -f /etc/environment ]; then source /etc/environment; fi
|
||||
if [ -f ${FILE_K3S_ENV} ]; then source ${FILE_K3S_ENV}; fi
|
||||
if [ -f /etc/environment ]; then . /etc/environment; fi
|
||||
if [ -f ${FILE_K3S_ENV} ]; then . ${FILE_K3S_ENV}; fi
|
||||
set +o allexport
|
||||
EOF
|
||||
$SUDO chmod 0755 ${FILE_K3S_SERVICE}
|
||||
|
@ -844,11 +1020,16 @@ EOF
|
|||
|
||||
# --- write systemd or openrc service file ---
|
||||
create_service_file() {
|
||||
[ "${HAS_SYSTEMD}" = true ] && create_systemd_service_file
|
||||
[ "${HAS_SYSTEMD}" = true ] && create_systemd_service_file && restore_systemd_service_file_context
|
||||
[ "${HAS_OPENRC}" = true ] && create_openrc_service_file
|
||||
return 0
|
||||
}
|
||||
|
||||
restore_systemd_service_file_context() {
|
||||
$SUDO restorecon -R -i ${FILE_K3S_SERVICE} 2>/dev/null || true
|
||||
$SUDO restorecon -R -i ${FILE_K3S_ENV} 2>/dev/null || true
|
||||
}
|
||||
|
||||
# --- get hashes of the current k3s bin and service files
|
||||
get_installed_hashes() {
|
||||
$SUDO sha256sum ${BIN_DIR}/k3s ${FILE_K3S_SERVICE} ${FILE_K3S_ENV} 2>&1 || true
|
||||
|
@ -877,6 +1058,19 @@ openrc_start() {
|
|||
$SUDO ${FILE_K3S_SERVICE} restart
|
||||
}
|
||||
|
||||
has_working_xtables() {
|
||||
if $SUDO sh -c "command -v \"$1-save\"" 1> /dev/null && $SUDO sh -c "command -v \"$1-restore\"" 1> /dev/null; then
|
||||
if $SUDO $1-save 2>/dev/null | grep -q '^-A CNI-HOSTPORT-MASQ -j MASQUERADE$'; then
|
||||
warn "Host $1-save/$1-restore tools are incompatible with existing rules"
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
info "Host $1-save/$1-restore tools not found"
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# --- startup systemd or openrc service ---
|
||||
service_enable_and_start() {
|
||||
if [ -f "/proc/cgroups" ] && [ "$(grep memory /proc/cgroups | while read -r n n n enabled; do echo $enabled; done)" -eq 0 ];
|
||||
|
@ -897,6 +1091,12 @@ service_enable_and_start() {
|
|||
return
|
||||
fi
|
||||
|
||||
for XTABLES in iptables ip6tables; do
|
||||
if has_working_xtables ${XTABLES}; then
|
||||
$SUDO ${XTABLES}-save 2>/dev/null | grep -v KUBE- | grep -iv flannel | $SUDO ${XTABLES}-restore
|
||||
fi
|
||||
done
|
||||
|
||||
[ "${HAS_SYSTEMD}" = true ] && systemd_start
|
||||
[ "${HAS_OPENRC}" = true ] && openrc_start
|
||||
return 0
|
||||
|
@ -919,3 +1119,4 @@ eval set -- $(escape "${INSTALL_K3S_EXEC}") $(quote "$@")
|
|||
create_service_file
|
||||
service_enable_and_start
|
||||
}
|
||||
|
||||
|
|
|
@ -1,480 +0,0 @@
|
|||
apiVersion: policy/v1beta1
|
||||
kind: PodSecurityPolicy
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
name: controller
|
||||
spec:
|
||||
allowPrivilegeEscalation: false
|
||||
allowedCapabilities: []
|
||||
allowedHostPaths: []
|
||||
defaultAddCapabilities: []
|
||||
defaultAllowPrivilegeEscalation: false
|
||||
fsGroup:
|
||||
ranges:
|
||||
- max: 65535
|
||||
min: 1
|
||||
rule: MustRunAs
|
||||
hostIPC: false
|
||||
hostNetwork: false
|
||||
hostPID: false
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: true
|
||||
requiredDropCapabilities:
|
||||
- ALL
|
||||
runAsUser:
|
||||
ranges:
|
||||
- max: 65535
|
||||
min: 1
|
||||
rule: MustRunAs
|
||||
seLinux:
|
||||
rule: RunAsAny
|
||||
supplementalGroups:
|
||||
ranges:
|
||||
- max: 65535
|
||||
min: 1
|
||||
rule: MustRunAs
|
||||
volumes:
|
||||
- configMap
|
||||
- secret
|
||||
- emptyDir
|
||||
---
|
||||
apiVersion: policy/v1beta1
|
||||
kind: PodSecurityPolicy
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
name: speaker
|
||||
spec:
|
||||
allowPrivilegeEscalation: false
|
||||
allowedCapabilities:
|
||||
- NET_RAW
|
||||
allowedHostPaths: []
|
||||
defaultAddCapabilities: []
|
||||
defaultAllowPrivilegeEscalation: false
|
||||
fsGroup:
|
||||
rule: RunAsAny
|
||||
hostIPC: false
|
||||
hostNetwork: true
|
||||
hostPID: false
|
||||
hostPorts:
|
||||
- max: 7472
|
||||
min: 7472
|
||||
- max: 7946
|
||||
min: 7946
|
||||
privileged: true
|
||||
readOnlyRootFilesystem: true
|
||||
requiredDropCapabilities:
|
||||
- ALL
|
||||
runAsUser:
|
||||
rule: RunAsAny
|
||||
seLinux:
|
||||
rule: RunAsAny
|
||||
supplementalGroups:
|
||||
rule: RunAsAny
|
||||
volumes:
|
||||
- configMap
|
||||
- secret
|
||||
- emptyDir
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
name: controller
|
||||
namespace: metallb-system
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
name: speaker
|
||||
namespace: metallb-system
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
name: metallb-system:controller
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- services
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- services/status
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
- apiGroups:
|
||||
- policy
|
||||
resourceNames:
|
||||
- controller
|
||||
resources:
|
||||
- podsecuritypolicies
|
||||
verbs:
|
||||
- use
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
name: metallb-system:speaker
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- services
|
||||
- endpoints
|
||||
- nodes
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups: ["discovery.k8s.io"]
|
||||
resources:
|
||||
- endpointslices
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- events
|
||||
verbs:
|
||||
- create
|
||||
- patch
|
||||
- apiGroups:
|
||||
- policy
|
||||
resourceNames:
|
||||
- speaker
|
||||
resources:
|
||||
- podsecuritypolicies
|
||||
verbs:
|
||||
- use
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
name: config-watcher
|
||||
namespace: metallb-system
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- configmaps
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
name: pod-lister
|
||||
namespace: metallb-system
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- pods
|
||||
verbs:
|
||||
- list
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
name: controller
|
||||
namespace: metallb-system
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- create
|
||||
- apiGroups:
|
||||
- ''
|
||||
resources:
|
||||
- secrets
|
||||
resourceNames:
|
||||
- memberlist
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- apps
|
||||
resources:
|
||||
- deployments
|
||||
resourceNames:
|
||||
- controller
|
||||
verbs:
|
||||
- get
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
name: metallb-system:controller
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: metallb-system:controller
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: controller
|
||||
namespace: metallb-system
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
name: metallb-system:speaker
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: metallb-system:speaker
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: speaker
|
||||
namespace: metallb-system
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
name: config-watcher
|
||||
namespace: metallb-system
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: config-watcher
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: controller
|
||||
- kind: ServiceAccount
|
||||
name: speaker
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
name: pod-lister
|
||||
namespace: metallb-system
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: pod-lister
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: speaker
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
name: controller
|
||||
namespace: metallb-system
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: controller
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: controller
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
component: speaker
|
||||
name: speaker
|
||||
namespace: metallb-system
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: metallb
|
||||
component: speaker
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
prometheus.io/port: '7472'
|
||||
prometheus.io/scrape: 'true'
|
||||
labels:
|
||||
app: metallb
|
||||
component: speaker
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- --port=7472
|
||||
- --config=config
|
||||
- --log-level=info
|
||||
env:
|
||||
- name: METALLB_NODE_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
- name: METALLB_HOST
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.hostIP
|
||||
- name: METALLB_ML_BIND_ADDR
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
# needed when another software is also using memberlist / port 7946
|
||||
# when changing this default you also need to update the container ports definition
|
||||
# and the PodSecurityPolicy hostPorts definition
|
||||
#- name: METALLB_ML_BIND_PORT
|
||||
# value: "7946"
|
||||
- name: METALLB_ML_LABELS
|
||||
value: "app=metallb,component=speaker"
|
||||
- name: METALLB_ML_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: memberlist
|
||||
key: secretkey
|
||||
image: quay.io/metallb/speaker:v0.12.1
|
||||
name: speaker
|
||||
ports:
|
||||
- containerPort: 7472
|
||||
name: monitoring
|
||||
- containerPort: 7946
|
||||
name: memberlist-tcp
|
||||
- containerPort: 7946
|
||||
name: memberlist-udp
|
||||
protocol: UDP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /metrics
|
||||
port: monitoring
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 1
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /metrics
|
||||
port: monitoring
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 1
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
add:
|
||||
- NET_RAW
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: true
|
||||
hostNetwork: true
|
||||
nodeSelector:
|
||||
kubernetes.io/os: linux
|
||||
serviceAccountName: speaker
|
||||
terminationGracePeriodSeconds: 2
|
||||
tolerations:
|
||||
- effect: NoSchedule
|
||||
key: node-role.kubernetes.io/master
|
||||
operator: Exists
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: metallb
|
||||
component: controller
|
||||
name: controller
|
||||
namespace: metallb-system
|
||||
spec:
|
||||
revisionHistoryLimit: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: metallb
|
||||
component: controller
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
prometheus.io/port: '7472'
|
||||
prometheus.io/scrape: 'true'
|
||||
labels:
|
||||
app: metallb
|
||||
component: controller
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- --port=7472
|
||||
- --config=config
|
||||
- --log-level=info
|
||||
env:
|
||||
- name: METALLB_ML_SECRET_NAME
|
||||
value: memberlist
|
||||
- name: METALLB_DEPLOYMENT
|
||||
value: controller
|
||||
image: quay.io/metallb/controller:v0.12.1
|
||||
name: controller
|
||||
ports:
|
||||
- containerPort: 7472
|
||||
name: monitoring
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /metrics
|
||||
port: monitoring
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 1
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /metrics
|
||||
port: monitoring
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 1
|
||||
successThreshold: 1
|
||||
failureThreshold: 3
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- all
|
||||
readOnlyRootFilesystem: true
|
||||
nodeSelector:
|
||||
kubernetes.io/os: linux
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65534
|
||||
fsGroup: 65534
|
||||
serviceAccountName: controller
|
||||
terminationGracePeriodSeconds: 0
|
|
@ -1,4 +1,5 @@
|
|||
# based on https://github.com/k3s-io/k3s/blob/master/manifests/traefik.yaml
|
||||
# required changes: global.systemDefaultRegistry must be set to ""
|
||||
---
|
||||
apiVersion: helm.cattle.io/v1
|
||||
kind: HelmChart
|
||||
|
@ -6,7 +7,7 @@ metadata:
|
|||
name: traefik-crd
|
||||
namespace: kube-system
|
||||
spec:
|
||||
chart: https://%{KUBERNETES_API}%/static/charts/traefik-crd-10.19.300.tgz
|
||||
chart: https://%{KUBERNETES_API}%/static/charts/traefik-crd-25.0.2+up25.0.0.tgz
|
||||
---
|
||||
apiVersion: helm.cattle.io/v1
|
||||
kind: HelmChart
|
||||
|
@ -14,16 +15,10 @@ metadata:
|
|||
name: traefik
|
||||
namespace: kube-system
|
||||
spec:
|
||||
chart: https://%{KUBERNETES_API}%/static/charts/traefik-10.19.300.tgz
|
||||
chart: https://%{KUBERNETES_API}%/static/charts/traefik-25.0.2+up25.0.0.tgz
|
||||
set:
|
||||
global.systemDefaultRegistry: ""
|
||||
valuesContent: |-
|
||||
rbac:
|
||||
enabled: true
|
||||
ports:
|
||||
websecure:
|
||||
tls:
|
||||
enabled: true
|
||||
podAnnotations:
|
||||
prometheus.io/port: "8082"
|
||||
prometheus.io/scrape: "true"
|
||||
|
@ -33,8 +28,8 @@ spec:
|
|||
enabled: true
|
||||
priorityClassName: "system-cluster-critical"
|
||||
image:
|
||||
name: "rancher/mirrored-library-traefik"
|
||||
tag: "2.6.2"
|
||||
repository: "rancher/mirrored-library-traefik"
|
||||
tag: "2.10.5"
|
||||
tolerations:
|
||||
- key: "CriticalAddonsOnly"
|
||||
operator: "Exists"
|
||||
|
@ -47,9 +42,4 @@ spec:
|
|||
service:
|
||||
ipFamilyPolicy: "PreferDualStack"
|
||||
annotations:
|
||||
metallb.universe.tf/allow-shared-ip: "shared-ip-service-group"
|
||||
metallb.universe.tf/address-pool: public
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
externalTrafficPolicy: Cluster
|
||||
|
||||
metallb.universe.tf/allow-shared-ip: shared-ip-service-group
|
||||
|
|
|
@ -11,4 +11,4 @@ command:
|
|||
- command: "python3 --version"
|
||||
- command: "pip3 --version"
|
||||
- command: "terraform --version"
|
||||
out: "1.0.8"
|
||||
out: "1.4.6"
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.domaindrivenarchitecture.provs.configuration.application
|
|||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.unmockkAll
|
||||
import org.domaindrivenarchitecture.provs.framework.core.*
|
||||
import org.domaindrivenarchitecture.provs.framework.core.cli.getPasswordToConfigureSudoWithoutPassword
|
||||
import org.domaindrivenarchitecture.provs.framework.core.docker.provideContainer
|
||||
|
@ -24,7 +24,7 @@ class ProvWithSudoKtTest {
|
|||
fun test_ensureSudoWithoutPassword_local_Prov() {
|
||||
|
||||
mockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||
every { getPasswordToConfigureSudoWithoutPassword() } returns Secret("testuserpw")
|
||||
every { getPasswordToConfigureSudoWithoutPassword() } returns Secret("testuser")
|
||||
|
||||
// given
|
||||
val containerName = "prov-test-sudo-no-pw"
|
||||
|
@ -49,7 +49,8 @@ class ProvWithSudoKtTest {
|
|||
assertFalse(canSudo1)
|
||||
assertTrue(canSudo2)
|
||||
|
||||
unmockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||
// cleanup
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
@ExtensiveContainerTest
|
||||
|
@ -57,7 +58,7 @@ class ProvWithSudoKtTest {
|
|||
|
||||
// given
|
||||
val containerName = "prov-test-sudo-no-pw-ssh"
|
||||
val password = Secret("testuserpw")
|
||||
val password = Secret("testuser")
|
||||
|
||||
val prov = Prov.newInstance(
|
||||
ContainerUbuntuHostProcessor(
|
||||
|
|
|
@ -13,7 +13,7 @@ import org.junit.jupiter.api.AfterAll
|
|||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class CliTargetCommandKtTest {
|
||||
internal class TargetCliCommandKtTest {
|
||||
|
||||
companion object {
|
||||
@BeforeAll
|
||||
|
@ -31,10 +31,8 @@ internal class CliTargetCommandKtTest {
|
|||
@AfterAll
|
||||
@JvmStatic
|
||||
internal fun afterAll() {
|
||||
unmockkObject(Prov)
|
||||
unmockkStatic(::local)
|
||||
unmockkStatic(::remote)
|
||||
unmockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||
// cleanup
|
||||
unmockkAll()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ internal class ApplicationKtTest {
|
|||
val dummyProv = Prov.newInstance(DummyProcessor())
|
||||
|
||||
mockkObject(Prov)
|
||||
every { Prov.newInstance(any(), any(), any(), any(), ) } returns dummyProv
|
||||
every { Prov.newInstance(any(), any(), any(), any()) } returns dummyProv
|
||||
|
||||
mockkStatic(::local)
|
||||
every { local() } returns dummyProv
|
||||
|
@ -52,7 +52,7 @@ internal class ApplicationKtTest {
|
|||
every { getConfig("testconfig.yaml") } returns testConfig
|
||||
|
||||
mockkStatic(Prov::provisionDesktop)
|
||||
every { any<Prov>().provisionDesktop(any(), any(), any(), any(), any(),any()) } returns ProvResult(
|
||||
every { any<Prov>().provisionDesktop(any(), any(), any(), any(), any()) } returns ProvResult(
|
||||
true,
|
||||
cmd = "mocked command"
|
||||
)
|
||||
|
@ -65,12 +65,8 @@ internal class ApplicationKtTest {
|
|||
@AfterAll
|
||||
@JvmStatic
|
||||
internal fun afterAll() {
|
||||
unmockkObject(Prov)
|
||||
unmockkStatic(::local)
|
||||
unmockkStatic(::remote)
|
||||
unmockkStatic(::getConfig)
|
||||
unmockkStatic(Prov::provisionDesktop)
|
||||
unmockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||
// cleanup
|
||||
unmockkAll()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +85,6 @@ internal class ApplicationKtTest {
|
|||
null,
|
||||
testConfig.gitUserName,
|
||||
testConfig.gitEmail,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +118,7 @@ internal class ApplicationKtTest {
|
|||
"Error: File\u001B[31m idontexist.yaml \u001B[0m was not found.Pls copy file \u001B[31m desktop-config-example.yaml \u001B[0m to file \u001B[31m idontexist.yaml \u001B[0m and change the content according to your needs.No suitable config found."
|
||||
assertEquals(expectedOutput, outContent.toString().replace("\r", "").replace("\n", ""))
|
||||
|
||||
verify(exactly = 0) { any<Prov>().provisionDesktop(any(), any(), any(), any(), any(), any()) }
|
||||
verify(exactly = 0) { any<Prov>().provisionDesktop(any(), any(), any(), any(), any()) }
|
||||
|
||||
unmockkStatic(::quit)
|
||||
}
|
||||
|
@ -157,7 +152,7 @@ internal class ApplicationKtTest {
|
|||
"Error: File \"src/test/resources/invalid-desktop-config.yaml\" has an invalid format and or invalid data.No suitable config found."
|
||||
assertEquals(expectedOutput, outContent.toString().replace("\r", "").replace("\n", ""))
|
||||
|
||||
verify(exactly = 0) { any<Prov>().provisionDesktop(any(), any(), any(), any(), any(), any()) }
|
||||
verify(exactly = 0) { any<Prov>().provisionDesktop(any(), any(), any(), any(), any()) }
|
||||
|
||||
unmockkStatic(::quit)
|
||||
}
|
||||
|
|
|
@ -14,13 +14,4 @@ internal class CliArgumentsParserTest {
|
|||
assertEquals(null, cli.configFile)
|
||||
assertEquals(true, cli.target.isValidLocalhost())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parse_cliCommand_with_onlyModule_teams_and_local_target() {
|
||||
val cli = CliArgumentsParser("test").parseCommand(args = arrayOf("ide", "local", "-o", "teams"))
|
||||
|
||||
assertTrue(cli.isValid())
|
||||
assertEquals(true, cli.target.isValidLocalhost())
|
||||
assertEquals(true, cli.onlyModules?.contains("teams"))
|
||||
}
|
||||
}
|
|
@ -1,12 +1,15 @@
|
|||
package org.domaindrivenarchitecture.provs.desktop.domain
|
||||
|
||||
import org.domaindrivenarchitecture.provs.framework.core.ProgressType
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||
import io.mockk.*
|
||||
import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
|
||||
import org.domaindrivenarchitecture.provs.desktop.infrastructure.installPpaFirefox
|
||||
import org.domaindrivenarchitecture.provs.desktop.infrastructure.verifyIdeSetup
|
||||
import org.domaindrivenarchitecture.provs.desktop.infrastructure.verifyOfficeSetup
|
||||
import org.domaindrivenarchitecture.provs.framework.core.*
|
||||
import org.domaindrivenarchitecture.provs.framework.core.docker.provideContainer
|
||||
import org.domaindrivenarchitecture.provs.framework.core.local
|
||||
import org.domaindrivenarchitecture.provs.framework.core.processors.ContainerStartMode
|
||||
import org.domaindrivenarchitecture.provs.framework.core.processors.ContainerUbuntuHostProcessor
|
||||
import org.domaindrivenarchitecture.provs.framework.core.remote
|
||||
import org.domaindrivenarchitecture.provs.framework.core.processors.DummyProcessor
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.deleteFile
|
||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||
|
@ -35,15 +38,76 @@ internal class DesktopServiceKtTest {
|
|||
|
||||
// when
|
||||
Assertions.assertThrows(Exception::class.java) {
|
||||
prov.provisionDesktop(
|
||||
DesktopType.BASIC,
|
||||
gitUserName = "testuser",
|
||||
gitEmail = "testuser@test.org",
|
||||
onlyModules = null
|
||||
prov.provisionDesktopCommand(
|
||||
DesktopCliCommand(DesktopType.BASIC, TargetCliCommand("testuser@somehost"), null), DesktopConfig() // dummy data
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun provisionDesktop_with_onlyModules_firefox_installs_firefox() {
|
||||
// given
|
||||
val prov = Prov.newInstance(DummyProcessor())
|
||||
mockkStatic(Prov::installPpaFirefox)
|
||||
every { any<Prov>().installPpaFirefox() } returns ProvResult(true, cmd = "mocked")
|
||||
|
||||
// when
|
||||
prov.provisionOnlyModules(DesktopType.IDE, onlyModules = listOf("firefox"))
|
||||
|
||||
// then
|
||||
verify(exactly = 1) { any<Prov>().installPpaFirefox() }
|
||||
|
||||
// cleanup
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun provisionDesktop_ide_with_onlyModules_verify_performs_verification() {
|
||||
// given
|
||||
val prov = Prov.newInstance(DummyProcessor())
|
||||
mockkStatic(Prov::verifyIdeSetup)
|
||||
mockkStatic(Prov::verifyOfficeSetup)
|
||||
mockkStatic(Prov::provisionBasicDesktop)
|
||||
every { any<Prov>().verifyIdeSetup() } returns ProvResult(true, cmd = "mocked")
|
||||
every { any<Prov>().verifyOfficeSetup() } returns ProvResult(true, cmd = "mocked")
|
||||
every { any<Prov>().provisionBasicDesktop(any(), any(), any(), any()) }
|
||||
|
||||
// when
|
||||
prov.provisionOnlyModules(DesktopType.IDE, onlyModules = listOf("verify"))
|
||||
|
||||
// then
|
||||
verify(exactly = 1) { any<Prov>().verifyIdeSetup() }
|
||||
verify(exactly = 0) { any<Prov>().verifyOfficeSetup() }
|
||||
verify(exactly = 0) { any<Prov>().provisionBasicDesktop(any(), any(), any(), any()) }
|
||||
|
||||
// cleanup
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun provisionDesktop_office_with_onlyModules_verify_performs_verification() {
|
||||
// given
|
||||
val prov = Prov.newInstance(DummyProcessor())
|
||||
mockkStatic(Prov::verifyIdeSetup)
|
||||
mockkStatic(Prov::verifyOfficeSetup)
|
||||
mockkStatic(Prov::provisionBasicDesktop)
|
||||
every { any<Prov>().verifyIdeSetup() } returns ProvResult(true, cmd = "mocked")
|
||||
every { any<Prov>().verifyOfficeSetup() } returns ProvResult(true, cmd = "mocked")
|
||||
every { any<Prov>().provisionBasicDesktop(any(), any(), any(), any()) }
|
||||
|
||||
// when
|
||||
prov.provisionOnlyModules(DesktopType.OFFICE, onlyModules = listOf("verify"))
|
||||
|
||||
// then
|
||||
verify(exactly = 0) { any<Prov>().verifyIdeSetup() }
|
||||
verify(exactly = 1) { any<Prov>().verifyOfficeSetup() }
|
||||
verify(exactly = 0) { any<Prov>().provisionBasicDesktop(any(), any(), any(), any()) }
|
||||
|
||||
// cleanup
|
||||
unmockkAll()
|
||||
}
|
||||
|
||||
|
||||
@ExtensiveContainerTest
|
||||
@Disabled("Takes very long, enable if you want to test a desktop setup")
|
||||
fun provisionDesktop() {
|
||||
|
@ -56,7 +120,6 @@ internal class DesktopServiceKtTest {
|
|||
DesktopType.BASIC,
|
||||
gitUserName = "testuser",
|
||||
gitEmail = "testuser@test.org",
|
||||
onlyModules = null
|
||||
)
|
||||
|
||||
// then
|
||||
|
@ -81,7 +144,6 @@ internal class DesktopServiceKtTest {
|
|||
DesktopType.IDE,
|
||||
gitUserName = "testuser",
|
||||
gitEmail = "testuser@test.org",
|
||||
onlyModules = null
|
||||
)
|
||||
|
||||
// then
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
package org.domaindrivenarchitecture.provs.desktop.domain
|
||||
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.deleteFile
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.addKnownHost
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.isKnownHost
|
||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
|
||||
class KnownHostTest {
|
||||
|
||||
@ContainerTest
|
||||
fun defaultKnownHosts() {
|
||||
// given
|
||||
val prov = defaultTestContainer()
|
||||
prov.task {
|
||||
aptInstall("ssh")
|
||||
deleteFile("~/.ssh/known_hosts")
|
||||
}
|
||||
|
||||
// when
|
||||
val res = prov.addKnownHosts()
|
||||
|
||||
// then
|
||||
assertTrue(res.success)
|
||||
}
|
||||
|
||||
|
||||
// Subclass of KnownHost for test knownHostSubclass_includes_additional_host
|
||||
class KnownHostsSubclass(hostName: String, port: Int?, hostKeys: List<HostKey>): KnownHost(hostName, port, hostKeys) {
|
||||
|
||||
companion object {
|
||||
val ANOTHER_HOST = KnownHostsSubclass("anotherhost.com", 2222, listOf("key1"))
|
||||
|
||||
fun values(): List<KnownHost> {
|
||||
return values + ANOTHER_HOST
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun knownHostSubclass_includes_additional_host() {
|
||||
// when
|
||||
val hosts = KnownHostsSubclass.values()
|
||||
|
||||
// then
|
||||
assertTrue(hosts.size > 1)
|
||||
assertEquals("key1", hosts.last().hostKeys[0])
|
||||
}
|
||||
|
||||
@ContainerTest
|
||||
fun knownHost_with_port_verified_successfully() {
|
||||
// given
|
||||
val prov = defaultTestContainer()
|
||||
prov.task {
|
||||
aptInstall("ssh")
|
||||
deleteFile("~/.ssh/known_hosts")
|
||||
}
|
||||
|
||||
// when
|
||||
assertFalse(prov.isKnownHost(KnownHost.GITHUB.hostName))
|
||||
assertFalse(prov.isKnownHost(KnownHost.GITHUB.hostName, 22))
|
||||
val res = prov.addKnownHost(KnownHost(KnownHost.GITHUB.hostName, 22, KnownHost.GITHUB.hostKeys), verifyKeys = true)
|
||||
|
||||
// then
|
||||
assertTrue(res.success)
|
||||
assertFalse(prov.isKnownHost(KnownHost.GITHUB.hostName))
|
||||
assertTrue(prov.isKnownHost(KnownHost.GITHUB.hostName, 22))
|
||||
}
|
||||
|
||||
@ContainerTest
|
||||
fun knownHost_with_port_verification_failing() {
|
||||
// given
|
||||
val prov = defaultTestContainer()
|
||||
prov.task {
|
||||
aptInstall("ssh")
|
||||
deleteFile("~/.ssh/known_hosts")
|
||||
}
|
||||
|
||||
// when
|
||||
assertFalse(prov.isKnownHost(KnownHost.GITHUB.hostName, 80))
|
||||
val res2 = prov.addKnownHost(KnownHost(KnownHost.GITHUB.hostName, 80, KnownHost.GITHUB.hostKeys), verifyKeys = true)
|
||||
|
||||
// then
|
||||
assertFalse(res2.success)
|
||||
assertFalse(prov.isKnownHost(KnownHost.GITHUB.hostName))
|
||||
assertFalse(prov.isKnownHost(KnownHost.GITHUB.hostName, 80))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||
|
||||
import org.domaindrivenarchitecture.provs.framework.core.getResourceAsText
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.checkFile
|
||||
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.framework.ubuntu.filesystem.base.*
|
||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
|
@ -48,4 +45,17 @@ internal class DevOpsKtTest {
|
|||
// then
|
||||
assertTrue(res.success)
|
||||
}
|
||||
|
||||
@ExtensiveContainerTest
|
||||
fun installKubeconform() {
|
||||
// given
|
||||
val prov = defaultTestContainer()
|
||||
|
||||
// when
|
||||
val res = prov.installKubeconform()
|
||||
|
||||
// then
|
||||
assertTrue(res.success)
|
||||
assertTrue(prov.checkFile("/usr/local/bin/kubeconform"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +1,60 @@
|
|||
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||
|
||||
import org.domaindrivenarchitecture.provs.framework.core.remote
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.deleteFile
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.fileContainsText
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.checkPackageInstalled
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.isPackageInstalled
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.PromptSecretSource
|
||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class FirefoxKtTest {
|
||||
|
||||
// Attention: this test does not test full functionality of installFirefox, e.g. does not test
|
||||
// remove snap, as this test runs against a container which does not have snap-firefox installed
|
||||
// Attention: this test does not test full functionality of installPpaFirefox, e.g. does not test
|
||||
// remove snap-firefox, as this test runs against a container, which does have neither snap nor snap-firefox installed
|
||||
@ExtensiveContainerTest
|
||||
fun installFirefox() {
|
||||
// given
|
||||
val prov = defaultTestContainer()
|
||||
|
||||
// when
|
||||
val result = defaultTestContainer().session {
|
||||
installFirefox()
|
||||
checkPackageInstalled("firefox")
|
||||
val result = prov.session {
|
||||
deleteFile("/etc/apt/apt.conf.d/51unattended-upgrades-firefox", sudo = true)
|
||||
deleteFile("/etc/apt/preferences.d/mozillateam", sudo = true)
|
||||
installPpaFirefox()
|
||||
}
|
||||
val result2 = prov.installPpaFirefox()
|
||||
|
||||
// then
|
||||
assertTrue(result.success)
|
||||
assertEquals("Firefox already installed with ppa", result2.out)
|
||||
|
||||
assertTrue(prov.isPackageInstalled("firefox"))
|
||||
assertTrue(
|
||||
prov.fileContainsText(
|
||||
"/etc/apt/apt.conf.d/51unattended-upgrades-firefox",
|
||||
"Unattended-Upgrade::Allowed-Origins:: \"LP-PPA-mozillateam:\${distro_codename}\";\n",
|
||||
sudo = true
|
||||
)
|
||||
)
|
||||
val expectedPolicyLine = Regex("1001? https?://ppa.launchpad(?:content)?.net/mozillateam/ppa/ubuntu")
|
||||
val policy = prov.cmd("apt policy firefox").out
|
||||
assertTrue(
|
||||
policy?.contains(expectedPolicyLine) ?: false,
|
||||
"$expectedPolicyLine was not found in $policy"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests installing firefox on a remote machine, e.g. a virtual machine
|
||||
*/
|
||||
@Test
|
||||
@Disabled("Update connection details,then enable and run manually")
|
||||
@Disabled("Update connection details, then enable the test and run manually")
|
||||
fun installFirefox_remotely() {
|
||||
val host = "192.168.56.123"
|
||||
val user = "user"
|
||||
|
@ -39,7 +67,7 @@ internal class FirefoxKtTest {
|
|||
/* remove for ssh authentication */
|
||||
PromptSecretSource("Remote password for user $user").secret()
|
||||
).session {
|
||||
installFirefox()
|
||||
installPpaFirefox()
|
||||
firefoxVersion = cmd("apt list firefox --installed").out ?: ""
|
||||
checkPackageInstalled("firefox")
|
||||
}
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||
import org.domaindrivenarchitecture.provs.framework.core.remote
|
||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||
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.gpgFingerprint
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.GopassSecretSource
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
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_keys.publicGPGSnakeoilKey
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
|
||||
|
||||
|
@ -21,7 +25,7 @@ internal class GopassKtTest {
|
|||
fun test_configureGopass_fails_with_path_starting_with_tilde() {
|
||||
// when
|
||||
val res = defaultTestContainer().task {
|
||||
deleteFile(".config/gopass/config.yml")
|
||||
deleteFile(".config/gopass/config")
|
||||
configureGopass("~/somedir")
|
||||
}
|
||||
|
||||
|
@ -40,36 +44,48 @@ internal class GopassKtTest {
|
|||
installGopass()
|
||||
configureGopass(prov.userHome() + gopassRootDir)
|
||||
gopassInitStoreFolder("~/exampleStoreFolder")
|
||||
gopassInitStoreFolder("~/exampleStoreFolder") // check idem-potency
|
||||
gopassMountStore("exampleStore", "~/exampleStoreFolder")
|
||||
gopassMountStore("exampleStore", "~/exampleStoreFolder") // check idem-potency
|
||||
prov.cmd("gopass ls")
|
||||
prov.cmd("gopass sync")
|
||||
}
|
||||
|
||||
// then
|
||||
prov.fileContent("~/.config/gopass/config.yml") // displays the content in the logs
|
||||
prov.fileContent("~/.config/gopass/config") // displays the content in the logs
|
||||
assertTrue(res.success)
|
||||
assertTrue(prov.fileContainsText("~/.config/gopass/config.yml", "/home/testuser/.password-store"))
|
||||
assertTrue(prov.fileContainsText("~/.config/gopass/config.yml", "exampleStore"))
|
||||
assertTrue(prov.fileContainsText("~/.config/gopass/config", "/home/testuser/.password-store"))
|
||||
assertTrue(prov.fileContainsText("~/.config/gopass/config", "exampleStore"))
|
||||
assertTrue(prov.checkDir(".git", gopassRootDir))
|
||||
}
|
||||
|
||||
@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() {
|
||||
// settings to change
|
||||
val host = "192.168.56.135"
|
||||
// host and user
|
||||
val host = "192.168.56.154"
|
||||
val user = "xxx"
|
||||
val pubKey = GopassSecretSource("path-to/pub.key").secret()
|
||||
val privateKey = GopassSecretSource("path-to/priv.key").secret()
|
||||
|
||||
// given
|
||||
val prov = remote(host, user)
|
||||
// connection by password
|
||||
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
|
||||
val res = prov.task {
|
||||
configureGpgKeys(
|
||||
KeyPair(
|
||||
pubKey,
|
||||
privateKey
|
||||
),
|
||||
KeyPair(pubKey, privateKey),
|
||||
trust = 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.ExtensiveContainerTest
|
||||
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
|
||||
class GraalVMKtTest {
|
||||
|
||||
@ExtensiveContainerTest
|
||||
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))
|
||||
}
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||
|
||||
import com.charleskorn.kaml.InvalidPropertyValueException
|
||||
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSourceType
|
||||
import org.domaindrivenarchitecture.provs.server.infrastructure.getK3sConfig
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
|
@ -34,7 +32,7 @@ internal class K3SDesktopConfigRepositoryKtTest {
|
|||
val exception = assertThrows<InvalidPropertyValueException> {
|
||||
getConfig("src/test/resources/invalid-desktop-config.yaml")
|
||||
}
|
||||
assertEquals("Value for 'sourceType' is invalid: Value 'xxx' is not a valid option, permitted choices are: FILE, GOPASS, PASS, PLAIN, PROMPT", exception.message)
|
||||
assertEquals("Value for 'sourceType' is invalid: Value 'xxx' is not a valid option, permitted choices are: ENV, FILE, GOPASS, PASS, PLAIN, PROMPT", exception.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -3,15 +3,15 @@ package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
|||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Disabled
|
||||
|
||||
class MsTeamsKtTest {
|
||||
|
||||
internal class NextcloudClientTest {
|
||||
@Disabled // test is hanging sometimes
|
||||
@ExtensiveContainerTest
|
||||
fun installMsTeams() {
|
||||
// given
|
||||
val a = defaultTestContainer()
|
||||
fun test_installNextcloudClient() {
|
||||
// when
|
||||
val res = a.task { installMsTeams() }
|
||||
val res = defaultTestContainer().installNextcloudClient()
|
||||
|
||||
// then
|
||||
assertTrue(res.success)
|
||||
}
|
|
@ -9,9 +9,9 @@ internal class PythonKtTest {
|
|||
@ExtensiveContainerTest
|
||||
fun test_provisionPython() {
|
||||
// when
|
||||
val res = defaultTestContainer().provisionPython()
|
||||
val result = defaultTestContainer().provisionPython()
|
||||
|
||||
// then
|
||||
assertTrue(res.success)
|
||||
assertTrue(result.success)
|
||||
}
|
||||
}
|
|
@ -1,35 +1,28 @@
|
|||
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.test.defaultTestContainer
|
||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.PromptSecretSource
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class VSCodeKtTest {
|
||||
|
||||
@ExtensiveContainerTest
|
||||
fun provisionAdditionalTools() {
|
||||
// given
|
||||
defaultTestContainer().aptInstall("curl unzip")
|
||||
|
||||
// when
|
||||
val res = defaultTestContainer().provisionAdditionalToolsForVSCode()
|
||||
|
||||
// then
|
||||
assertTrue(res.success)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("Test currently not working, needs fix. VSC is installed by snapd which is not currently supported to run inside docker")
|
||||
fun installVSC() {
|
||||
@Disabled("Remote testing by updating connection details below, then enable test and run it manually.")
|
||||
// Remark: VSCode installs with snap, which does not run in container and cannot be tested by container test.
|
||||
fun installVSCode() {
|
||||
// given
|
||||
val a = defaultTestContainer()
|
||||
a.aptInstall("xvfb libgbm-dev libasound2")
|
||||
val prov = remote("192.168.56.153", "xx", PromptSecretSource("Remote password").secret()) // machine needs openssh-server installed and sudo possible without pw
|
||||
prov.aptInstall("xvfb libgbm-dev libasound2")
|
||||
|
||||
// when
|
||||
val res = a.installVSC("python", "clojure")
|
||||
val res = prov.task {
|
||||
installVSCode("python", "clojure")
|
||||
cmd("code -v")
|
||||
cmd("codium -v")
|
||||
}
|
||||
|
||||
// then
|
||||
assertTrue(res.success)
|
||||
|
|
|
@ -33,33 +33,34 @@ internal class ProvTest {
|
|||
assertTrue(res)
|
||||
}
|
||||
|
||||
@ContainerTest
|
||||
@Test
|
||||
fun sh() {
|
||||
// given
|
||||
val script = """
|
||||
# test some script commands
|
||||
|
||||
ping -c1 hetzner.com
|
||||
echo something
|
||||
echo something1
|
||||
pwd
|
||||
echo something3
|
||||
"""
|
||||
|
||||
// when
|
||||
val res = Prov.newInstance(name = "testing").sh(script).success
|
||||
val res = Prov.newInstance(name = "provs_test").sh(script).success
|
||||
|
||||
// then
|
||||
assertTrue(res)
|
||||
}
|
||||
|
||||
@ContainerTest
|
||||
@NonCi
|
||||
@NonCi // sudo might not be available
|
||||
fun sh_with_dir_and_sudo() {
|
||||
// given
|
||||
val script = """
|
||||
# test some script commands
|
||||
|
||||
ping -c1 hetzner.com
|
||||
echo something
|
||||
echo 1 # comment behind command
|
||||
pwd
|
||||
echo something1
|
||||
echo something2 # with comment behind command
|
||||
"""
|
||||
|
||||
// when
|
||||
|
@ -404,7 +405,6 @@ internal class ProvTest {
|
|||
println(outContent.toString())
|
||||
|
||||
val expectedOutput =
|
||||
"WARNING: method task should not be used at top-level, use method <session> instead.\n" +
|
||||
"============================================== SUMMARY (test instance with no progress info) =============================================\n" +
|
||||
"> \u001B[92mSuccess\u001B[0m -- taskA \n" +
|
||||
"---> \u001B[92mSuccess\u001B[0m -- prov_marks_failed_output_yellow_if_optional (optional) \n" +
|
||||
|
@ -475,7 +475,6 @@ internal class ProvTest {
|
|||
println(outContent.toString())
|
||||
|
||||
val expectedOutput =
|
||||
"WARNING: method task should not be used at top-level, use method <session> instead.\n" +
|
||||
"============================================== SUMMARY (test instance) =============================================\n" +
|
||||
"> \u001B[92mSuccess\u001B[0m -- TaskB \n" +
|
||||
"---> \u001B[92mSuccess\u001B[0m -- taskC \n" +
|
||||
|
@ -722,7 +721,6 @@ internal class ProvTest {
|
|||
println(outContent.toString())
|
||||
|
||||
val expectedOutput =
|
||||
"WARNING: method taskWithResult should not be used at top-level, use method <session> instead.\n" +
|
||||
"============================================== SUMMARY (test instance with no progress info) =============================================\n" +
|
||||
"> \u001B[91mFAILED\u001B[0m -- testMethodForOutputTest_with_returned_results \n" +
|
||||
"---> \u001B[91mFAILED\u001B[0m -- sub1 \n" +
|
||||
|
@ -791,9 +789,9 @@ internal class ProvTest {
|
|||
System.setErr(PrintStream(errContent))
|
||||
|
||||
// when
|
||||
Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.NONE)
|
||||
Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.BASIC)
|
||||
.tst_task().success
|
||||
Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.NONE)
|
||||
Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.BASIC)
|
||||
.tst_task().success // test that also second run gets warning
|
||||
|
||||
// then
|
||||
|
@ -804,6 +802,11 @@ internal class ProvTest {
|
|||
|
||||
val expectedOutputOneRun =
|
||||
"WARNING: method task should not be used at top-level, use method <session> instead.\n" +
|
||||
"---------- Processing started ----------\n" +
|
||||
"> \u001B[90mexecuting...\u001B[0m -- tst_task\n" +
|
||||
"---> \u001B[90mexecuting...\u001B[0m -- task_returningTrue\n" +
|
||||
"---> \u001B[90mexecuting...\u001B[0m -- task_returningFalse\n" +
|
||||
"---------- Processing completed ----------\n" +
|
||||
"============================================== SUMMARY (test instance with no progress info) =============================================\n" +
|
||||
"> \u001B[91mFAILED\u001B[0m -- tst_task \n" +
|
||||
"---> \u001B[92mSuccess\u001B[0m -- task_returningTrue \n" +
|
||||
|
|
|
@ -103,7 +103,8 @@ internal class UbuntuProvTest {
|
|||
|
||||
// then
|
||||
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 {
|
||||
return """
|
||||
FROM ubuntu:18.04
|
||||
FROM ubuntu:22.04
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
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
|
||||
CMD /bin/bash
|
||||
|
|
|
@ -34,7 +34,7 @@ class ContainerUbuntuHostProcessorTest {
|
|||
|
||||
// given
|
||||
val containerName = "prov-test-ssh-with-container"
|
||||
val password = Secret("testuserpw")
|
||||
val password = Secret("testuser")
|
||||
|
||||
val prov = Prov.newInstance(
|
||||
ContainerUbuntuHostProcessor(
|
||||
|
@ -57,8 +57,8 @@ class ContainerUbuntuHostProcessorTest {
|
|||
val remoteProvBySsh = remote(ipOfContainer, "testuser", password)
|
||||
|
||||
// when
|
||||
val firstSessionResult = remoteProvBySsh.cmd("echo 1")
|
||||
val secondSessionResult = remoteProvBySsh.cmd("echo 1")
|
||||
val firstSessionResult = remoteProvBySsh.cmd("echo 1") // connect (to container) by ssh via ip
|
||||
val secondSessionResult = remoteProvBySsh.cmd("echo 2") // second connect after first connection has been closed
|
||||
|
||||
// then
|
||||
assertTrue(firstSessionResult.success)
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.domaindrivenarchitecture.provs.framework.core.escapeProcentForPrintf
|
|||
import org.domaindrivenarchitecture.provs.framework.core.escapeSingleQuoteForShell
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.nio.file.Paths
|
||||
|
||||
|
||||
internal class LocalProcessorTest {
|
||||
|
@ -24,6 +25,18 @@ internal class LocalProcessorTest {
|
|||
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
|
||||
fun cmd_with_nested_shell_and_printf() {
|
||||
|
@ -59,7 +72,7 @@ internal class LocalProcessorTest {
|
|||
|
||||
|
||||
@Test
|
||||
fun cmd_forUnkownCommand_resultWithError() {
|
||||
fun cmd_forUnknownCommand_resultWithError() {
|
||||
// given
|
||||
val prov = Prov.newInstance()
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue