Compare commits
No commits in common. "66470ed57a365650632d2af4da0328024d4819da" and "bf436bf846c915333b6be1c844ffd33fbfea4c41" have entirely different histories.
66470ed57a
...
bf436bf846
92 changed files with 823 additions and 5539 deletions
107
.gitlab-ci.yml
107
.gitlab-ci.yml
|
@ -5,23 +5,20 @@ stages:
|
||||||
- test
|
- test
|
||||||
- package
|
- package
|
||||||
- publish
|
- publish
|
||||||
- release
|
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- echo "---------- Start CI ----------"
|
- echo "---------- Start CI ----------"
|
||||||
- export GRADLE_USER_HOME=`pwd`/.gradle
|
- export GRADLE_USER_HOME=`pwd`/.gradle
|
||||||
- chmod +x gradlew
|
- chmod +x gradlew
|
||||||
- echo "------ commit info ---------------"
|
- echo "------ commit tag ---------------"
|
||||||
- echo $CI_COMMIT_TAG
|
- echo $CI_COMMIT_TAG
|
||||||
- echo $CI_COMMIT_REF_NAME
|
- echo $CI_COMMIT_REF_NAME
|
||||||
- echo "----------------------------------"
|
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
paths:
|
paths:
|
||||||
- .gradle/wrapper
|
- .gradle/wrapper
|
||||||
- .gradle/caches
|
- .gradle/caches
|
||||||
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
|
@ -32,7 +29,6 @@ build:
|
||||||
- build/libs/*.jar
|
- build/libs/*.jar
|
||||||
expire_in: 1 week
|
expire_in: 1 week
|
||||||
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
image: docker:latest
|
image: docker:latest
|
||||||
|
@ -50,7 +46,7 @@ test:
|
||||||
- docker build --pull -t "$CI_REGISTRY_IMAGE" .
|
- 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
|
- docker run --privileged -dit --name provs_test -v /var/run/docker.sock:/var/run/docker.sock $CI_REGISTRY_IMAGE
|
||||||
- docker inspect -f '{{.State.Running}}' provs_test
|
- docker inspect -f '{{.State.Running}}' provs_test
|
||||||
- ./gradlew -x assemble test -Dtestdockerwithoutsudo=true -DexcludeTags=extensivecontainertest,nonci
|
- ./gradlew -x assemble test -Dtestdockerwithoutsudo=true -DexcludeTags=extensivecontainertest
|
||||||
artifacts:
|
artifacts:
|
||||||
when: on_failure
|
when: on_failure
|
||||||
paths:
|
paths:
|
||||||
|
@ -58,51 +54,84 @@ test:
|
||||||
reports:
|
reports:
|
||||||
junit: build/test-results/test/TEST-*.xml
|
junit: build/test-results/test/TEST-*.xml
|
||||||
|
|
||||||
|
.fatjars:
|
||||||
package:
|
|
||||||
stage: package
|
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:
|
||||||
|
stage: package
|
||||||
|
rules:
|
||||||
|
- if: $CI_PIPELINE_SOURCE != "push"
|
||||||
|
when: never
|
||||||
|
- if: $CI_COMMIT_TAG =~ /^release-[0-9]+[.][0-9]+([.][0-9]+)?$/
|
||||||
script:
|
script:
|
||||||
- ./gradlew -x assemble -x test jar
|
|
||||||
- ./gradlew -x assemble -x test -x jar uberjarDesktop
|
- ./gradlew -x assemble -x test -x jar uberjarDesktop
|
||||||
- ./gradlew -x assemble -x test -x jar uberjarServer
|
- ./gradlew -x assemble -x test -x jar uberjarServer
|
||||||
- ./gradlew -x assemble -x test -x jar uberjarSyspec
|
- ./gradlew -x assemble -x test -x jar uberjarSyspec
|
||||||
- cd build/libs/
|
- cd build/libs/
|
||||||
- find . -type f -exec sha256sum {} \; | sort > sha256sum.lst
|
- find . -type f -exec sha256sum {} \; | sort > sha256sum.lst
|
||||||
- find . -type f -exec sha512sum {} \; | sort > sha512sum.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
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- build/libs/*.jar
|
- build/libs/*.jar
|
||||||
- build/libs/*.lst
|
|
||||||
|
|
||||||
|
publish-released-lib:
|
||||||
publish-maven-package-to-gitlab:
|
|
||||||
stage: publish
|
stage: publish
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_PIPELINE_SOURCE != "push"
|
- if: $CI_PIPELINE_SOURCE != "push"
|
||||||
when: never
|
when: never
|
||||||
- if: $CI_COMMIT_TAG !~ /^release-[0-9]+[.][0-9]+([.][0-9]+)?$/
|
- if: $CI_COMMIT_TAG =~ /^release-[0-9]+[.][0-9]+([.][0-9]+)?$/
|
||||||
script:
|
script:
|
||||||
- ./gradlew -x assemble -x test publishLibraryPublicationToGitlabRepository
|
- ./gradlew -x assemble -x test jar
|
||||||
|
- ./gradlew -x assemble -x test publish
|
||||||
|
artifacts:
|
||||||
|
paths:
|
||||||
|
- build/libs/*.jar
|
||||||
|
|
||||||
|
release:
|
||||||
publish-maven-package-to-meissa:
|
|
||||||
stage: publish
|
|
||||||
allow_failure: true
|
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE != "push"
|
|
||||||
when: never
|
|
||||||
- if: $CI_COMMIT_TAG !~ /^release-[0-9]+[.][0-9]+([.][0-9]+)?$/
|
|
||||||
script:
|
|
||||||
- apt-get update -y
|
|
||||||
- apt-get install -y iputils-ping ssh
|
|
||||||
- ping -c 2 repo.prod.meissa.de
|
|
||||||
- ssh-keyscan repo.prod.meissa.de
|
|
||||||
- ./gradlew -x assemble -x test publishLibraryPublicationToMeissaRepository
|
|
||||||
|
|
||||||
|
|
||||||
release-to-gitlab:
|
|
||||||
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||||
stage: release
|
stage: publish
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_PIPELINE_SOURCE != "push"
|
- if: $CI_PIPELINE_SOURCE != "push"
|
||||||
when: never
|
when: never
|
||||||
|
@ -124,19 +153,5 @@ release-to-gitlab:
|
||||||
--assets-link "{\"name\":\"sha256sum.lst\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/provs/-/jobs/${CI_JOB_ID}/artifacts/file/build/libs/sha256sum.lst\"}" \
|
--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\"}" \
|
--assets-link "{\"name\":\"sha512sum.lst\",\"url\":\"https://gitlab.com/domaindrivenarchitecture/provs/-/jobs/${CI_JOB_ID}/artifacts/file/build/libs/sha512sum.lst\"}" \
|
||||||
|
|
||||||
|
|
||||||
release-to-meissa:
|
|
||||||
stage: release
|
|
||||||
allow_failure: true
|
|
||||||
rules:
|
|
||||||
- if: $CI_PIPELINE_SOURCE != "push"
|
|
||||||
when: never
|
|
||||||
- if: $CI_COMMIT_TAG =~ /^release-[0-9]+[.][0-9]+([.][0-9]+)?$/
|
|
||||||
script:
|
|
||||||
- apt-get update
|
|
||||||
- apt-get -yqq install curl
|
|
||||||
- ./gradlew createReleaseAndUploadAssets
|
|
||||||
|
|
||||||
|
|
||||||
after_script:
|
after_script:
|
||||||
- echo "---------- End CI ----------"
|
- echo "---------- End CI ----------"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="provision-basic-desktop" type="JetRunConfigurationType">
|
<configuration default="false" name="provs-desktop" type="JetRunConfigurationType">
|
||||||
<option name="MAIN_CLASS_NAME" value="org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" />
|
<option name="MAIN_CLASS_NAME" value="org.domaindrivenarchitecture.provs.desktop.application.ApplicationKt" />
|
||||||
<module name="provs.main" />
|
<module name="provs.main" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="basic user@192.168.56.146 -p" />
|
<option name="PROGRAM_PARAMETERS" value="basic local -o provsbinaries" />
|
||||||
<shortenClasspath name="NONE" />
|
<shortenClasspath name="NONE" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Make" enabled="true" />
|
<option name="Make" enabled="true" />
|
|
@ -3,8 +3,7 @@ FROM ubuntu:latest
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
RUN apt-get update && apt-get -y install apt-utils sudo
|
RUN apt-get update && apt-get -y install apt-utils sudo
|
||||||
|
RUN useradd -m testuser && echo "testuser:testuser" | chpasswd && adduser testuser sudo
|
||||||
RUN useradd -m testuser && echo "testuser:testuserpw" | chpasswd && adduser testuser sudo
|
|
||||||
RUN echo "testuser ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/testuser
|
RUN echo "testuser ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/testuser
|
||||||
|
|
||||||
USER testuser
|
USER testuser
|
||||||
|
|
|
@ -60,6 +60,7 @@ After having installed `provs-desktop.jar` (see prerequisites) execute:
|
||||||
* `-o` for only executing one action, e.g.
|
* `-o` for only executing one action, e.g.
|
||||||
* `-o verify` for verifying your installation
|
* `-o verify` for verifying your installation
|
||||||
* `-o firefox` to install firefox from apt on ubuntu
|
* `-o firefox` to install firefox from apt on ubuntu
|
||||||
|
* `-o teams` to install MS-Teams
|
||||||
|
|
||||||
|
|
||||||
#### Example
|
#### Example
|
||||||
|
|
127
build.gradle
127
build.gradle
|
@ -1,25 +1,24 @@
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = "1.7.20"
|
ext.kotlin_version = "1.7.0"
|
||||||
ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID
|
ext.CI_PROJECT_ID = System.env.CI_PROJECT_ID
|
||||||
|
|
||||||
repositories {
|
repositories { mavenCentral() }
|
||||||
mavenCentral()
|
|
||||||
|
dependencies {
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
apply plugin: "org.jetbrains.kotlin.jvm"
|
||||||
id "org.jetbrains.kotlin.jvm" version "$kotlin_version"
|
apply plugin: "java-library"
|
||||||
id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version"
|
apply plugin: "java-test-fixtures"
|
||||||
id "java"
|
|
||||||
id "java-test-fixtures"
|
|
||||||
}
|
|
||||||
|
|
||||||
apply plugin: "maven-publish"
|
apply plugin: "maven-publish"
|
||||||
|
apply plugin: "kotlinx-serialization"
|
||||||
|
|
||||||
|
|
||||||
group = "org.domaindrivenarchitecture.provs"
|
group = "org.domaindrivenarchitecture.provs"
|
||||||
version = "0.24.2-SNAPSHOT"
|
version = "0.17.1-SNAPSHOT"
|
||||||
|
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -27,19 +26,11 @@ repositories {
|
||||||
|
|
||||||
|
|
||||||
java {
|
java {
|
||||||
// https://stackoverflow.com/questions/21904269/configure-gradle-to-publish-sources-and-javadoc
|
|
||||||
withSourcesJar()
|
|
||||||
withJavadocJar()
|
|
||||||
|
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(11)
|
languageVersion = JavaLanguageVersion.of(11)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
|
||||||
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
test {
|
test {
|
||||||
// set properties for the tests
|
// set properties for the tests
|
||||||
|
@ -66,6 +57,12 @@ compileJava.options.debugOptions.debugLevel = "source,lines,vars"
|
||||||
compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars"
|
compileTestFixturesJava.options.debugOptions.debugLevel = "source,lines,vars"
|
||||||
compileTestJava.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 {
|
dependencies {
|
||||||
|
|
||||||
|
@ -74,7 +71,7 @@ dependencies {
|
||||||
api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2")
|
api("org.jetbrains.kotlinx:kotlinx-serialization-core:1.3.2")
|
||||||
api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4")
|
api("org.jetbrains.kotlinx:kotlinx-cli:0.3.4")
|
||||||
|
|
||||||
api('com.charleskorn.kaml:kaml:0.54.0')
|
api('com.charleskorn.kaml:kaml:0.43.0')
|
||||||
|
|
||||||
api("org.slf4j:slf4j-api:1.7.36")
|
api("org.slf4j:slf4j-api:1.7.36")
|
||||||
api('ch.qos.logback:logback-classic:1.2.11')
|
api('ch.qos.logback:logback-classic:1.2.11')
|
||||||
|
@ -92,7 +89,7 @@ dependencies {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
tasks.register('uberjarDesktop', Jar) {
|
task uberjarDesktop(type: Jar) {
|
||||||
|
|
||||||
from sourceSets.main.output
|
from sourceSets.main.output
|
||||||
|
|
||||||
|
@ -113,7 +110,7 @@ tasks.register('uberjarDesktop', Jar) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
tasks.register('uberjarServer', Jar) {
|
task uberjarServer(type: Jar) {
|
||||||
|
|
||||||
from sourceSets.main.output
|
from sourceSets.main.output
|
||||||
|
|
||||||
|
@ -134,7 +131,7 @@ tasks.register('uberjarServer', Jar) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
tasks.register('uberjarSyspec', Jar) {
|
task uberjarSyspec(type: Jar) {
|
||||||
|
|
||||||
from sourceSets.main.output
|
from sourceSets.main.output
|
||||||
|
|
||||||
|
@ -158,7 +155,7 @@ def projectRoot = rootProject.projectDir
|
||||||
|
|
||||||
// copy jar to /usr/local/bin and make it executable
|
// 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)
|
// Remark: to be able to use it you must have jarwrapper installed (sudo apt install jarwrapper)
|
||||||
tasks.register('installlocally') {
|
task installlocally {
|
||||||
dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec)
|
dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec)
|
||||||
doLast {
|
doLast {
|
||||||
exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") }
|
exec { commandLine("sh", "-c", "sudo apt-get update & sudo apt-get install jarwrapper") }
|
||||||
|
@ -171,21 +168,24 @@ tasks.register('installlocally') {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task sourceJar(type: Jar, dependsOn: classes) {
|
||||||
|
from sourceSets.main.allSource
|
||||||
|
archiveClassifier.set("sources")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// publish to repo.prod.meissa.de with task "publishLibraryPublicationToMeissaRepository" -- (using pattern "publishLibraryPublicationTo<MAVEN REPOSITORY NAME>Repository")
|
|
||||||
publishing {
|
publishing {
|
||||||
publications {
|
publications {
|
||||||
library(MavenPublication) {
|
library(MavenPublication) {
|
||||||
groupId 'org.domaindrivenarchitecture'
|
|
||||||
artifactId 'provs'
|
|
||||||
from components.java
|
from components.java
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
if (System.getenv("CI_JOB_TOKEN") != null) {
|
||||||
|
// see https://docs.gitlab.com/ee/user/packages/maven_repository/index.html
|
||||||
maven {
|
maven {
|
||||||
name = "gitlab"
|
url "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven"
|
||||||
url = "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/packages/maven"
|
name "GitLab"
|
||||||
credentials(HttpHeaderCredentials) {
|
credentials(HttpHeaderCredentials) {
|
||||||
name = "Job-Token"
|
name = "Job-Token"
|
||||||
value = System.getenv("CI_JOB_TOKEN")
|
value = System.getenv("CI_JOB_TOKEN")
|
||||||
|
@ -194,71 +194,8 @@ publishing {
|
||||||
header(HttpHeaderAuthentication)
|
header(HttpHeaderAuthentication)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maven {
|
|
||||||
name = "meissa"
|
|
||||||
url = uri("https://repo.prod.meissa.de/api/packages/meissa/maven")
|
|
||||||
|
|
||||||
credentials(HttpHeaderCredentials) {
|
|
||||||
name = "Authorization"
|
|
||||||
if (System.getenv("CI_JOB_TOKEN") != null) {
|
|
||||||
def tokenFromEnv = System.getenv("RELEASE_TOKEN")
|
|
||||||
if (tokenFromEnv == null) {
|
|
||||||
println "Error: RELEASE_TOKEN not found"
|
|
||||||
} else {
|
} else {
|
||||||
value = "token " + tokenFromEnv
|
mavenLocal()
|
||||||
println "RELEASE_TOKEN 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("RELEASE_TOKEN")) {
|
|
||||||
// if RELEASE_TOKEN is missing, provide a dummy in order to avoid error "Could not get unknown property 'RELEASE_TOKEN' for Credentials [header: Authorization]" for other gradle tasks
|
|
||||||
ext.RELEASE_TOKEN = "RELEASE_TOKEN not provided in file \".gradle/gradle.properties\""
|
|
||||||
println "Error: RELEASE_TOKEN not found"
|
|
||||||
}
|
|
||||||
value = "token $RELEASE_TOKEN"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
authentication {
|
|
||||||
header(HttpHeaderAuthentication)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
tasks.register('createReleaseAndUploadAssets') {
|
|
||||||
dependsOn(uberjarServer, uberjarDesktop, uberjarSyspec)
|
|
||||||
doLast {
|
|
||||||
|
|
||||||
def token = project.properties.get("RELEASE_TOKEN") ?: System.getenv("RELEASE_TOKEN")
|
|
||||||
if (token == null) {
|
|
||||||
throw new GradleException('No token found.')
|
|
||||||
}
|
|
||||||
|
|
||||||
def output1 = new ByteArrayOutputStream()
|
|
||||||
exec {
|
|
||||||
standardOutput = output1
|
|
||||||
def TAG = project.version
|
|
||||||
commandLine("sh", "-c", "curl -X 'POST' 'https://repo.prod.meissa.de/api/v1/repos/meissa/provs/releases' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{ \"body\": \"Provides jar-files for release $TAG\\nAttention: The \\\"Source Code\\\"-files below are not up-to-date!\", \"tag_name\": \"$TAG\" }' -H \"Authorization: token $token\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
def matches = output1 =~ /\{"id":(\d+?),/
|
|
||||||
if (!matches) {
|
|
||||||
throw new GradleException('id of release could not be parsed in: ' + output1)
|
|
||||||
}
|
|
||||||
|
|
||||||
def releaseId = matches.group(1)
|
|
||||||
println "Release=$releaseId"
|
|
||||||
|
|
||||||
def releaseApiUrl = "https://repo.prod.meissa.de/api/v1/repos/meissa/provs/releases"
|
|
||||||
exec { commandLine("sh", "-c", "find build/libs/ -type f -exec sha256sum {} \\; | sort > build/libs/sha256sum.lst") }
|
|
||||||
exec { commandLine("sh", "-c", "find build/libs/ -type f -exec sha512sum {} \\; | sort > build/libs/sha512sum.lst") }
|
|
||||||
exec { commandLine("sh", "-c", "curl -X 'POST' '$releaseApiUrl/$releaseId/assets' -H 'accept: application/json' -H \"Authorization: token $token\" -H 'Content-Type: multipart/form-data' -F 'attachment=@build/libs/provs-desktop.jar;type=application/x-java-archive'") }
|
|
||||||
exec { commandLine("sh", "-c", "curl -X 'POST' '$releaseApiUrl/$releaseId/assets' -H 'accept: application/json' -H \"Authorization: token $token\" -H 'Content-Type: multipart/form-data' -F 'attachment=@build/libs/provs-server.jar;type=application/x-java-archive'") }
|
|
||||||
exec { commandLine("sh", "-c", "curl -X 'POST' '$releaseApiUrl/$releaseId/assets' -H 'accept: application/json' -H \"Authorization: token $token\" -H 'Content-Type: multipart/form-data' -F 'attachment=@build/libs/provs-syspec.jar;type=application/x-java-archive'") }
|
|
||||||
exec { commandLine("sh", "-c", "curl -X 'POST' '$releaseApiUrl/$releaseId/assets' -H 'accept: application/json' -H \"Authorization: token $token\" -H 'Content-Type: multipart/form-data' -F 'attachment=@build/libs/sha256sum.lst;type=text/plain'") }
|
|
||||||
exec { commandLine("sh", "-c", "curl -X 'POST' '$releaseApiUrl/$releaseId/assets' -H 'accept: application/json' -H \"Authorization: token $token\" -H 'Content-Type: multipart/form-data' -F 'attachment=@build/libs/sha512sum.lst;type=text/plain'") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
63
doc/CliApplication.md
Normal file
63
doc/CliApplication.md
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
|
||||||
|
autonumber
|
||||||
|
|
||||||
|
skinparam sequenceBox {
|
||||||
|
borderColor White
|
||||||
|
}
|
||||||
|
|
||||||
|
participant User
|
||||||
|
|
||||||
|
box "application" #LightBlue
|
||||||
|
|
||||||
|
participant CliWorkplace
|
||||||
|
participant CliWorkplaceParser
|
||||||
|
participant CliWorkplaceCommand
|
||||||
|
participant Application
|
||||||
|
|
||||||
|
end box
|
||||||
|
|
||||||
|
box #White
|
||||||
|
|
||||||
|
participant CliUtils
|
||||||
|
participant "Prov (local or remote...)" as ProvInstance
|
||||||
|
|
||||||
|
end box
|
||||||
|
|
||||||
|
box "domain" #LightGreen
|
||||||
|
|
||||||
|
participant ProvisionWorkplace
|
||||||
|
|
||||||
|
end box
|
||||||
|
|
||||||
|
box "infrastructure" #CornSilk
|
||||||
|
|
||||||
|
participant ConfigRepository
|
||||||
|
participant "Infrastructure functions" as Infrastructure_functions
|
||||||
|
|
||||||
|
end box
|
||||||
|
|
||||||
|
|
||||||
|
User -> CliWorkplace ++ : main(args...)
|
||||||
|
|
||||||
|
CliWorkplace -> CliWorkplaceParser : parseWorkplaceArguments
|
||||||
|
|
||||||
|
CliWorkplace -> CliWorkplaceCommand : isValid ?
|
||||||
|
|
||||||
|
CliWorkplace -> ConfigRepository : getConfig
|
||||||
|
|
||||||
|
CliWorkplace -> CliUtils : createProvInstance
|
||||||
|
ProvInstance <- CliUtils : create
|
||||||
|
|
||||||
|
CliWorkplace -> Application : provision ( config )
|
||||||
|
Application -> ProvInstance : provisionWorkplace ( type, ssh, ...)
|
||||||
|
ProvInstance -> ProvisionWorkplace : provisionWorkplace
|
||||||
|
|
||||||
|
ProvisionWorkplace -> Infrastructure_functions: Various calls like:
|
||||||
|
ProvisionWorkplace -> Infrastructure_functions: install ssh, gpg, git ...
|
||||||
|
ProvisionWorkplace -> Infrastructure_functions: installVirtualBoxGuestAdditions
|
||||||
|
ProvisionWorkplace -> Infrastructure_functions: configureNoSwappiness, ...
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
```
|
|
@ -1,55 +0,0 @@
|
||||||
```plantuml
|
|
||||||
@startuml
|
|
||||||
|
|
||||||
autonumber
|
|
||||||
|
|
||||||
skinparam sequenceBox {
|
|
||||||
borderColor White
|
|
||||||
}
|
|
||||||
|
|
||||||
participant Cli
|
|
||||||
participant Application
|
|
||||||
participant CliArgumentsParser
|
|
||||||
participant CliTargetCommand
|
|
||||||
participant CliUtils
|
|
||||||
participant "CliUtils\ncreateLocalProv" as CliUtilsL
|
|
||||||
participant "CliUtils\ncreateRemoteProv" as CliUtilsR
|
|
||||||
participant Prov
|
|
||||||
participant PromptSecretSource
|
|
||||||
participant User
|
|
||||||
|
|
||||||
Cli -> Application ++ : main(args...)
|
|
||||||
Application -> CliArgumentsParser : parseCommand
|
|
||||||
|
|
||||||
CliArgumentsParser -> CliTargetCommand : create()
|
|
||||||
Application -> CliUtils : createProvInstance( targetCliCommand )
|
|
||||||
alt target.isValidLocal
|
|
||||||
CliUtils -> CliUtilsL : createLocalProv
|
|
||||||
CliUtilsL -> Prov : createLocalInstance
|
|
||||||
alt userCannotSudoWithoutPw
|
|
||||||
CliUtilsL -> PromptSecretSource : getPassword
|
|
||||||
CliUtilsL -> User : makeUserSudoWithoutPw
|
|
||||||
CliUtilsL --> CliUtils : provInstance
|
|
||||||
CliUtils --> Application : provInstance
|
|
||||||
end
|
|
||||||
else target.isValidRemote
|
|
||||||
CliUtils -> CliUtilsR : createRemoteProv
|
|
||||||
CliUtilsR -> Prov : createRemoteInstance
|
|
||||||
alt userCannotSudoWithoutPw
|
|
||||||
CliUtilsR -> PromptSecretSource : getPassword
|
|
||||||
CliUtilsR -> User : makeUserSudoWithoutPw
|
|
||||||
CliUtilsR -> Prov : createRemoteInstance\n[new ssh-client is required]
|
|
||||||
CliUtilsR --> CliUtils : provInstance
|
|
||||||
CliUtils --> Application : provInstance
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Application -> DesktopService1 : provisionDesktopCommand ( provInstance, desktopCliCommand )
|
|
||||||
|
|
||||||
|
|
||||||
'DesktopService1 -> DesktopService2 : provisionDesktop( config )
|
|
||||||
'DesktopService1 -> ConfigRepository : getConfig
|
|
||||||
|
|
||||||
@enduml
|
|
||||||
|
|
||||||
```
|
|
|
@ -1,38 +0,0 @@
|
||||||
```plantuml
|
|
||||||
@startuml
|
|
||||||
|
|
||||||
autonumber
|
|
||||||
|
|
||||||
skinparam sequenceBox {
|
|
||||||
borderColor White
|
|
||||||
}
|
|
||||||
|
|
||||||
participant User
|
|
||||||
|
|
||||||
User -> Application ++ : main(args...)
|
|
||||||
Application -> CliArgumentsParser : create
|
|
||||||
CliArgumentsParser -> ArgParser : subcommands
|
|
||||||
Application -> CliArgumentsParser : parseCommand
|
|
||||||
CliArgumentsParser -> ArgParser : super.parse
|
|
||||||
|
|
||||||
CliArgumentsParser -> CliTargetCommand : create()
|
|
||||||
CliTargetCommand -> CliTargetCommand : parseRemoteTarget
|
|
||||||
alt passwordInteractive == true
|
|
||||||
CliTargetCommand -> PromptSecretSource : prompt-for-password
|
|
||||||
end
|
|
||||||
CliArgumentsParser -> DesktopCliCommand : create(desktopType, cliTargetCmd, ...)
|
|
||||||
CliArgumentsParser --> Application: desktopCliCommand
|
|
||||||
Application -> DesktopCliCommand : isValid ?
|
|
||||||
Application -> CliUtils : createProvInstance
|
|
||||||
alt target.isValidLocal
|
|
||||||
CliUtils -> CliUtils : createLocalProv
|
|
||||||
else target.isValidRemote
|
|
||||||
CliUtils -> CliUtils : createRemote
|
|
||||||
end
|
|
||||||
Application -> DesktopService1 : provisionDesktopCommand ( provInstance, desktopCliCommand )
|
|
||||||
DesktopService1 -> DesktopService2 : provisionDesktop( config )
|
|
||||||
DesktopService1 -> ConfigRepository : getConfig
|
|
||||||
|
|
||||||
@enduml
|
|
||||||
|
|
||||||
```
|
|
|
@ -29,6 +29,6 @@ The success or failure is computed automatically in the following way:
|
||||||
|
|
||||||
## Call hierarchy
|
## Call hierarchy
|
||||||
|
|
||||||
In the following link you can find an example of a sequence diagram when provisioning a desktop:
|
Find below an example of a sequence diagram when provisioning a desktop workplace:
|
||||||
|
|
||||||
[ProvisionDesktopSequence.md](ProvisionDesktopSequence.md)
|
![img.png](resources/provision-workplace-sequence.diagram.png)
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
# 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/$(curl 'https://go.dev/VERSION?m=text').linux-amd64.tar.gz
|
|
||||||
|
|
||||||
extract latest version to ~/go
|
|
||||||
tar -C ~ -xzf go*.linux-amd64.tar.gz
|
|
||||||
|
|
||||||
APPEND='export PATH=$PATH:$HOME/go/bin'
|
|
||||||
|
|
||||||
echo $APPEND >> $HOME/.profile
|
|
||||||
|
|
||||||
## VScode optional - TODO!?!
|
|
||||||
Go extension autoinstall
|
|
||||||
install gpls, div, etc.
|
|
||||||
|
|
||||||
## 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
|
|
|
@ -1,49 +0,0 @@
|
||||||
```plantuml
|
|
||||||
@startuml
|
|
||||||
|
|
||||||
autonumber
|
|
||||||
|
|
||||||
skinparam sequenceBox {
|
|
||||||
borderColor White
|
|
||||||
}
|
|
||||||
|
|
||||||
participant User
|
|
||||||
|
|
||||||
box "application" #LightBlue
|
|
||||||
participant Application
|
|
||||||
participant CliArgumentsParser
|
|
||||||
participant DesktopCliCommand
|
|
||||||
end box
|
|
||||||
|
|
||||||
box #White
|
|
||||||
participant CliUtils
|
|
||||||
participant "Prov (local or remote...)" as ProvInstance
|
|
||||||
end box
|
|
||||||
|
|
||||||
box "domain" #LightGreen
|
|
||||||
participant "DesktopService\n.provisionDesktopCommand" as DesktopService1
|
|
||||||
participant "DesktopService\n.provisionDesktop" as DesktopService2
|
|
||||||
end box
|
|
||||||
|
|
||||||
box "infrastructure" #CornSilk
|
|
||||||
participant ConfigRepository
|
|
||||||
participant "Various\ninfrastructure functions" as Infrastructure_functions
|
|
||||||
end box
|
|
||||||
|
|
||||||
|
|
||||||
User -> Application ++ : main(args...)
|
|
||||||
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, ...
|
|
||||||
|
|
||||||
@enduml
|
|
||||||
```
|
|
|
@ -28,3 +28,45 @@ In the following document we describe how we implement idempotence:
|
||||||
|
|
||||||
https://gitlab.com/domaindrivenarchitecture/overview/-/blob/master/adr-provs/quasi-idempotence.md
|
https://gitlab.com/domaindrivenarchitecture/overview/-/blob/master/adr-provs/quasi-idempotence.md
|
||||||
|
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Multiple architectural layers provide different levels of functionality:
|
||||||
|
|
||||||
|
![provs layers](resources/provs-architecture-7.png "Provs architecture")
|
||||||
|
|
||||||
|
|
||||||
|
## Module structure
|
||||||
|
|
||||||
|
For the modules we use domain-drive design according to:
|
||||||
|
|
||||||
|
https://gitlab.com/domaindrivenarchitecture/overview/-/blob/master/adr-provs/ddd-structure.md
|
||||||
|
|
||||||
|
## Module dependencies
|
||||||
|
|
||||||
|
![resources/prov-module-dependencies-5b.png](resources/prov-module-dependencies-5b.png)
|
||||||
|
|
||||||
|
__Explanation__:
|
||||||
|
|
||||||
|
Modules:
|
||||||
|
|
||||||
|
<ol type="A">
|
||||||
|
<li>Common module: has both a domain layer and an infrastructure layer</li>
|
||||||
|
<li>Module with only domain layer: e.g. for very simple logic where no infrastructure layer is needed</li>
|
||||||
|
<li>Module with only infrastructure layer: these are often _utility modules_, which provide a collection of utility functions</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
|
||||||
|
1. Domain layer calls (a function in) the infrastructure layer of the same module
|
||||||
|
* _Common practice of dependencies within a module_
|
||||||
|
1. Domain layer calls (a function in) the domain layer another module
|
||||||
|
* _Common practice of dependencies between modules_
|
||||||
|
1. Base layer calls domain layer
|
||||||
|
* _Usually not recommended!_
|
||||||
|
4. Domain layer calls infrastructure layer in another module
|
||||||
|
* _This sometimes can make sense, e.g. if module B just needs some low-level function of module D instead of full provisioning.
|
||||||
|
However, in most cases it is recommended to call the domain layer of module D whenever possible_
|
||||||
|
5. Domain layer calls infrastructure layer in another module, which only has infrastructure layer
|
||||||
|
* _Common practice for calling utility modules, which don't have a domain layer._
|
||||||
|
|
||||||
|
|
BIN
doc/resources/prov-module-dependencies-5b.png
Normal file
BIN
doc/resources/prov-module-dependencies-5b.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
BIN
doc/resources/provision-workplace-sequence.diagram.png
Normal file
BIN
doc/resources/provision-workplace-sequence.diagram.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
BIN
doc/resources/provs-architecture-7.png
Normal file
BIN
doc/resources/provs-architecture-7.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 100 KiB |
|
@ -3,6 +3,7 @@ package org.domaindrivenarchitecture.provs.configuration.application
|
||||||
import kotlinx.cli.ArgParser
|
import kotlinx.cli.ArgParser
|
||||||
import kotlinx.cli.ArgType
|
import kotlinx.cli.ArgType
|
||||||
import kotlinx.cli.default
|
import kotlinx.cli.default
|
||||||
|
import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
|
||||||
|
|
||||||
open class CliTargetParser(name: String) : ArgParser(name) {
|
open class CliTargetParser(name: String) : ArgParser(name) {
|
||||||
val target by argument(
|
val target by argument(
|
||||||
|
@ -16,3 +17,13 @@ open class CliTargetParser(name: String) : ArgParser(name) {
|
||||||
"prompt for password for remote target",
|
"prompt for password for remote target",
|
||||||
).default(false)
|
).default(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun parseTarget(
|
||||||
|
programName: String = "provs",
|
||||||
|
args: Array<String>
|
||||||
|
): TargetCliCommand {
|
||||||
|
val parser = CliTargetParser(programName)
|
||||||
|
parser.parse(args)
|
||||||
|
|
||||||
|
return TargetCliCommand(parser.target, parser.passwordInteractive)
|
||||||
|
}
|
|
@ -1,21 +0,0 @@
|
||||||
package org.domaindrivenarchitecture.provs.configuration.application
|
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.cli.getPasswordToConfigureSudoWithoutPassword
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.currentUserCanSudoWithoutPassword
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.makeCurrentUserSudoerWithoutPasswordRequired
|
|
||||||
|
|
||||||
|
|
||||||
fun Prov.ensureSudoWithoutPassword(password: Secret?) {
|
|
||||||
|
|
||||||
if (!currentUserCanSudoWithoutPassword()) {
|
|
||||||
val passwordNonNull = password ?: getPasswordToConfigureSudoWithoutPassword()
|
|
||||||
|
|
||||||
val result = makeCurrentUserSudoerWithoutPasswordRequired(passwordNonNull)
|
|
||||||
|
|
||||||
check(result.success) {
|
|
||||||
"Could not make user a sudoer without password required. (E.g. the password provided may be incorrect.)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,7 +7,7 @@ import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileReposit
|
||||||
class DefaultConfigFileRepository : ConfigFileRepository {
|
class DefaultConfigFileRepository : ConfigFileRepository {
|
||||||
override fun assertExists(configFileName: ConfigFileName?) {
|
override fun assertExists(configFileName: ConfigFileName?) {
|
||||||
if (configFileName != null && !checkLocalFile(configFileName.fullqualified())) {
|
if (configFileName != null && !checkLocalFile(configFileName.fullqualified())) {
|
||||||
throw RuntimeException("Config file not found. Please check if path is correct.")
|
throw RuntimeException("Config file ${configFileName.fileName} not found. Please check if path is correct.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
package org.domaindrivenarchitecture.provs.desktop.application
|
package org.domaindrivenarchitecture.provs.desktop.application
|
||||||
|
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import org.domaindrivenarchitecture.provs.configuration.application.ensureSudoWithoutPassword
|
|
||||||
import org.domaindrivenarchitecture.provs.desktop.domain.DesktopConfig
|
|
||||||
import org.domaindrivenarchitecture.provs.desktop.domain.provisionDesktopCommand
|
import org.domaindrivenarchitecture.provs.desktop.domain.provisionDesktopCommand
|
||||||
import org.domaindrivenarchitecture.provs.desktop.infrastructure.getConfig
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.cli.createProvInstance
|
import org.domaindrivenarchitecture.provs.framework.core.cli.createProvInstance
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.cli.quit
|
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.nio.file.Files
|
|
||||||
import kotlin.io.path.Path
|
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,39 +17,19 @@ fun main(args: Array<String>) {
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val defaultConfigFileName = "desktop-config.yaml"
|
val prov = createProvInstance(cmd.target, remoteHostSetSudoWithoutPasswordRequired = true)
|
||||||
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 {
|
try {
|
||||||
getConfig(configFileName)
|
provisionDesktopCommand(prov, cmd)
|
||||||
} catch (e: SerializationException) {
|
} catch (e: SerializationException) {
|
||||||
println(
|
println(
|
||||||
"Error: File \"${configFileName}\" has an invalid format and or invalid data."
|
"Error: File \"${cmd.configFile?.fileName}\" has an invalid format and or invalid data.\n"
|
||||||
)
|
)
|
||||||
null
|
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
println(
|
println(
|
||||||
"Error: File\u001b[31m ${configFileName} \u001b[0m was not found.\n" +
|
"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 ${configFileName} \u001B[0m " +
|
"Pls copy file \u001B[31m desktop-config-example.yaml \u001B[0m to file \u001B[31m ${cmd.configFile?.fileName} \u001B[0m " +
|
||||||
"and change the content according to your needs."
|
"and change the content according to your needs.\n"
|
||||||
)
|
)
|
||||||
null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config == null) {
|
|
||||||
println("No suitable config found.")
|
|
||||||
quit(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
val prov = createProvInstance(cmd.target)
|
|
||||||
|
|
||||||
prov.session {
|
|
||||||
ensureSudoWithoutPassword(cmd.target.remoteTarget()?.password)
|
|
||||||
provisionDesktopCommand(cmd, config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package org.domaindrivenarchitecture.provs.desktop.application
|
package org.domaindrivenarchitecture.provs.desktop.application
|
||||||
|
|
||||||
import kotlinx.cli.ArgType
|
import kotlinx.cli.ArgType
|
||||||
import kotlinx.cli.ExperimentalCli
|
|
||||||
import kotlinx.cli.Subcommand
|
import kotlinx.cli.Subcommand
|
||||||
import org.domaindrivenarchitecture.provs.configuration.application.CliTargetParser
|
import org.domaindrivenarchitecture.provs.configuration.application.CliTargetParser
|
||||||
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
||||||
|
@ -11,7 +10,6 @@ import org.domaindrivenarchitecture.provs.desktop.domain.DesktopOnlyModule
|
||||||
import org.domaindrivenarchitecture.provs.desktop.domain.DesktopType
|
import org.domaindrivenarchitecture.provs.desktop.domain.DesktopType
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCli::class)
|
|
||||||
open class CliArgumentsParser(name: String) : CliTargetParser(name) {
|
open class CliArgumentsParser(name: String) : CliTargetParser(name) {
|
||||||
|
|
||||||
private val modules: List<DesktopSubcommand> = listOf(Basic(), Office(), Ide())
|
private val modules: List<DesktopSubcommand> = listOf(Basic(), Office(), Ide())
|
||||||
|
@ -25,14 +23,12 @@ open class CliArgumentsParser(name: String) : CliTargetParser(name) {
|
||||||
|
|
||||||
val module = modules.first { it.parsed }
|
val module = modules.first { it.parsed }
|
||||||
|
|
||||||
val targetCliCommand = TargetCliCommand(
|
|
||||||
target,
|
|
||||||
passwordInteractive
|
|
||||||
)
|
|
||||||
|
|
||||||
return DesktopCliCommand(
|
return DesktopCliCommand(
|
||||||
DesktopType.valueOf(module.name.uppercase()),
|
DesktopType.valueOf(module.name.uppercase()),
|
||||||
targetCliCommand,
|
TargetCliCommand(
|
||||||
|
target,
|
||||||
|
passwordInteractive
|
||||||
|
),
|
||||||
module.configFileName,
|
module.configFileName,
|
||||||
module.onlyModules
|
module.onlyModules
|
||||||
)
|
)
|
||||||
|
@ -59,7 +55,7 @@ open class CliArgumentsParser(name: String) : CliTargetParser(name) {
|
||||||
override fun execute() {
|
override fun execute() {
|
||||||
configFileName = cliConfigFileName?.let { ConfigFileName(it) }
|
configFileName = cliConfigFileName?.let { ConfigFileName(it) }
|
||||||
parsed = true
|
parsed = true
|
||||||
onlyModules = only?.let { listOf(it.name.lowercase()) }
|
onlyModules = if (only != null) listOf(only!!.name.lowercase()) else null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
package org.domaindrivenarchitecture.provs.desktop.domain
|
package org.domaindrivenarchitecture.provs.desktop.domain
|
||||||
|
|
||||||
enum class DesktopOnlyModule {
|
enum class DesktopOnlyModule {
|
||||||
FIREFOX, VERIFY
|
TEAMS, FIREFOX, VERIFY
|
||||||
}
|
}
|
|
@ -9,11 +9,15 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.KeyPair
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.SshKeyPair
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.SshKeyPair
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.gpgFingerprint
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.gpgFingerprint
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.provisionKeys
|
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.currentUserCanSudo
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.whoami
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.whoami
|
||||||
|
|
||||||
internal fun Prov.provisionDesktopCommand(cmd: DesktopCliCommand, conf: DesktopConfig) = task {
|
internal fun provisionDesktopCommand(prov: Prov, cmd: DesktopCliCommand) {
|
||||||
provisionDesktop(
|
|
||||||
|
// retrieve config
|
||||||
|
val conf = if (cmd.configFile != null) getConfig(cmd.configFile.fileName) else DesktopConfig()
|
||||||
|
|
||||||
|
prov.provisionDesktop(
|
||||||
cmd.type,
|
cmd.type,
|
||||||
conf.ssh?.keyPair(),
|
conf.ssh?.keyPair(),
|
||||||
conf.gpg?.keyPair(),
|
conf.gpg?.keyPair(),
|
||||||
|
@ -61,7 +65,7 @@ internal fun Prov.provisionDesktop(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.validatePrecondition() {
|
fun Prov.validatePrecondition() {
|
||||||
if (!currentUserCanSudoWithoutPassword()) {
|
if (!currentUserCanSudo()) {
|
||||||
throw Exception("Current user ${whoami()} cannot execute sudo without entering a password! This is necessary to execute provisionDesktop")
|
throw Exception("Current user ${whoami()} cannot execute sudo without entering a password! This is necessary to execute provisionDesktop")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,22 +94,31 @@ fun Prov.provisionIdeDesktop(onlyModules: List<String>? = null) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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) {
|
fun Prov.provisionOfficeDesktop(onlyModules: List<String>? = null) {
|
||||||
if (onlyModules == null) {
|
if (onlyModules == null) {
|
||||||
aptInstall(ZIP_UTILS)
|
aptInstall(ZIP_UTILS)
|
||||||
aptInstall(SPELLCHECKING_DE)
|
|
||||||
aptInstall(BROWSER)
|
aptInstall(BROWSER)
|
||||||
aptInstall(EMAIL_CLIENT)
|
aptInstall(EMAIL_CLIENT)
|
||||||
installDeltaChat()
|
installDeltaChat()
|
||||||
aptInstall(OFFICE_SUITE)
|
aptInstall(OFFICE_SUITE)
|
||||||
installZimWiki()
|
installZimWiki()
|
||||||
installNextcloudClient()
|
installNextcloudClient()
|
||||||
aptInstall(COMPARE_TOOLS)
|
|
||||||
|
|
||||||
// optional as installation of these tools often fail and they are not considered mandatory
|
// optional as installation of these tools often fail and they are not considered mandatory
|
||||||
optional {
|
optional {
|
||||||
aptInstall(DRAWING_TOOLS)
|
aptInstall(DRAWING_TOOLS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aptInstall(SPELLCHECKING_DE)
|
||||||
} else if (onlyModules.contains(DesktopOnlyModule.VERIFY.name.lowercase())) {
|
} else if (onlyModules.contains(DesktopOnlyModule.VERIFY.name.lowercase())) {
|
||||||
verifyOfficeSetup()
|
verifyOfficeSetup()
|
||||||
} else if (onlyModules.contains(DesktopOnlyModule.FIREFOX.name.lowercase())) {
|
} else if (onlyModules.contains(DesktopOnlyModule.FIREFOX.name.lowercase())) {
|
||||||
|
@ -142,13 +155,11 @@ fun Prov.provisionBasicDesktop(
|
||||||
|
|
||||||
installFirefox()
|
installFirefox()
|
||||||
installGopass()
|
installGopass()
|
||||||
configureGopass(publicGpgKey = gpg?.publicKey)
|
installGopassBridgeJsonApi()
|
||||||
installGopassJsonApi()
|
|
||||||
downloadGopassBridge()
|
downloadGopassBridge()
|
||||||
|
|
||||||
installRedshift()
|
installRedshift()
|
||||||
configureRedshift()
|
|
||||||
|
|
||||||
|
configureRedshift()
|
||||||
configureNoSwappiness()
|
configureNoSwappiness()
|
||||||
configureBash()
|
configureBash()
|
||||||
installVirtualBoxGuestAdditions()
|
installVirtualBoxGuestAdditions()
|
||||||
|
|
|
@ -11,8 +11,8 @@ import java.io.FileWriter
|
||||||
* Returns DesktopConfig; data for config is read from specified file.
|
* Returns DesktopConfig; data for config is read from specified file.
|
||||||
* Throws exceptions FileNotFoundException, SerializationException if file is not found resp. cannot be parsed.
|
* Throws exceptions FileNotFoundException, SerializationException if file is not found resp. cannot be parsed.
|
||||||
*/
|
*/
|
||||||
fun getConfig(filename: String): DesktopConfig = readFromFile(filename).yamlToType()
|
internal fun getConfig(filename: String = "desktop-config.yaml"): DesktopConfig = readFromFile(filename).yamlToType()
|
||||||
|
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun writeConfig(config: DesktopConfig, fileName: String = "desktop-config.yaml") = FileWriter(fileName).use { it.write(config.toYaml()) }
|
internal fun writeConfig(config: DesktopConfig, fileName: String = "desktop-config.yaml") = FileWriter(fileName).use { it.write(config.toYaml()) }
|
||||||
|
|
|
@ -7,9 +7,7 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInsta
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.web.base.downloadFromURL
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.web.base.downloadFromURL
|
||||||
|
|
||||||
|
|
||||||
private const val RESOURCE_PATH = "org/domaindrivenarchitecture/provs/desktop/infrastructure"
|
private const val resourcePath = "org/domaindrivenarchitecture/provs/desktop/infrastructure"
|
||||||
private const val KUBE_CONFIG_CONTEXT_SCRIPT = ".bashrc.d/kubectl.sh"
|
|
||||||
|
|
||||||
|
|
||||||
fun Prov.installDevOps() = task {
|
fun Prov.installDevOps() = task {
|
||||||
installTerraform()
|
installTerraform()
|
||||||
|
@ -43,9 +41,20 @@ fun Prov.installYq(
|
||||||
fun Prov.installKubectlAndTools(): ProvResult = task {
|
fun Prov.installKubectlAndTools(): ProvResult = task {
|
||||||
|
|
||||||
task("installKubectl") {
|
task("installKubectl") {
|
||||||
if (!checkFile(KUBE_CONFIG_CONTEXT_SCRIPT)) {
|
val kubeConfigFile = ".bashrc.d/kubectl.sh"
|
||||||
installKubectl()
|
if (!checkFile(kubeConfigFile)) {
|
||||||
configureKubectlBashCompletion()
|
// prerequisites -- see https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/
|
||||||
|
cmd("sudo apt-get update")
|
||||||
|
aptInstall("apt-transport-https ca-certificates curl")
|
||||||
|
cmd("sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg")
|
||||||
|
cmd("echo \"deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main\" | sudo tee /etc/apt/sources.list.d/kubernetes.list")
|
||||||
|
|
||||||
|
// kubectl and bash completion
|
||||||
|
cmd("sudo apt update")
|
||||||
|
aptInstall("kubectl")
|
||||||
|
cmd("kubectl completion bash >> /etc/bash_completion.d/kubernetes", sudo = true)
|
||||||
|
createDir(".bashrc.d")
|
||||||
|
createFileFromResource(kubeConfigFile, "kubectl.sh", resourcePath)
|
||||||
} else {
|
} else {
|
||||||
ProvResult(true, out = "Kubectl already installed")
|
ProvResult(true, out = "Kubectl already installed")
|
||||||
}
|
}
|
||||||
|
@ -54,47 +63,20 @@ fun Prov.installKubectlAndTools(): ProvResult = task {
|
||||||
installDevopsScripts()
|
installDevopsScripts()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.installKubectl(): ProvResult = task {
|
|
||||||
|
|
||||||
// see https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/
|
|
||||||
val kubectlVersion = "1.23.0"
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
cmd("sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl", dir = tmpDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Prov.configureKubectlBashCompletion(): ProvResult = 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("install ssh helper") {
|
task("install ssh helper") {
|
||||||
createFileFromResource(
|
createFileFromResource(
|
||||||
"/usr/local/bin/sshu.sh",
|
"/usr/local/bin/sshu.sh",
|
||||||
"sshu.sh",
|
"sshu.sh",
|
||||||
RESOURCE_PATH,
|
resourcePath,
|
||||||
"555",
|
"555",
|
||||||
sudo = true
|
sudo = true
|
||||||
)
|
)
|
||||||
createFileFromResource(
|
createFileFromResource(
|
||||||
"/usr/local/bin/ssht.sh",
|
"/usr/local/bin/ssht.sh",
|
||||||
"ssht.sh",
|
"ssht.sh",
|
||||||
RESOURCE_PATH,
|
resourcePath,
|
||||||
"555",
|
"555",
|
||||||
sudo = true
|
sudo = true
|
||||||
)
|
)
|
||||||
|
@ -105,7 +87,7 @@ fun Prov.installDevopsScripts() {
|
||||||
createFileFromResource(
|
createFileFromResource(
|
||||||
k3sContextFile,
|
k3sContextFile,
|
||||||
"k3s-create-context.sh",
|
"k3s-create-context.sh",
|
||||||
RESOURCE_PATH,
|
resourcePath,
|
||||||
"555",
|
"555",
|
||||||
sudo = true
|
sudo = true
|
||||||
)
|
)
|
||||||
|
@ -116,13 +98,12 @@ fun Prov.installDevopsScripts() {
|
||||||
createFileFromResource(
|
createFileFromResource(
|
||||||
k3sConnectFile,
|
k3sConnectFile,
|
||||||
"k3s-connect.sh",
|
"k3s-connect.sh",
|
||||||
RESOURCE_PATH,
|
resourcePath,
|
||||||
"555",
|
"555",
|
||||||
sudo = true
|
sudo = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.installTerraform(): ProvResult = task {
|
fun Prov.installTerraform(): ProvResult = task {
|
||||||
val dir = "/usr/lib/tfenv/"
|
val dir = "/usr/lib/tfenv/"
|
||||||
|
|
||||||
|
@ -133,8 +114,8 @@ fun Prov.installTerraform(): ProvResult = task {
|
||||||
cmd("ln -s " + dir + "bin/* /usr/local/bin", sudo = true)
|
cmd("ln -s " + dir + "bin/* /usr/local/bin", sudo = true)
|
||||||
}
|
}
|
||||||
cmd("tfenv install", sudo = true)
|
cmd("tfenv install", sudo = true)
|
||||||
cmd("tfenv install latest:^1.4.6", sudo = true)
|
cmd("tfenv install latest:^1.0.8", sudo = true)
|
||||||
cmd("tfenv use latest:^1.4.6", sudo = true)
|
cmd("tfenv use latest:^1.0.8", sudo = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,16 +13,12 @@ fun Prov.installFirefox() = task {
|
||||||
|
|
||||||
// inspired by: https://www.omgubuntu.co.uk/2022/04/how-to-install-firefox-deb-apt-ubuntu-22-04
|
// inspired by: https://www.omgubuntu.co.uk/2022/04/how-to-install-firefox-deb-apt-ubuntu-22-04
|
||||||
|
|
||||||
task("remove snap firefox") {
|
|
||||||
if (chk("snap list | grep firefox")) {
|
if (chk("snap list | grep firefox")) {
|
||||||
cmd("snap remove firefox", sudo = true)
|
cmd("snap remove firefox", sudo = true)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
aptInstall("software-properties-common")
|
aptInstall("software-properties-common")
|
||||||
cmd("add-apt-repository -y ppa:mozillateam/ppa", sudo = true)
|
cmd("add-apt-repository -y ppa:mozillateam/ppa", sudo = true)
|
||||||
|
|
||||||
// set prio in order to use ppa-firefox above snap
|
|
||||||
addTextToFile(
|
addTextToFile(
|
||||||
"\nPackage: *\n" +
|
"\nPackage: *\n" +
|
||||||
"Pin: release o=LP-PPA-mozillateam\n" +
|
"Pin: release o=LP-PPA-mozillateam\n" +
|
||||||
|
@ -38,5 +34,4 @@ fun Prov.installFirefox() = task {
|
||||||
)
|
)
|
||||||
|
|
||||||
aptInstall("firefox")
|
aptInstall("firefox")
|
||||||
cmd("apt-get upgrade -y --allow-downgrades firefox", sudo = true)
|
|
||||||
}
|
}
|
|
@ -2,18 +2,16 @@ package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.ProvResult
|
import org.domaindrivenarchitecture.provs.framework.core.ProvResult
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.*
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.*
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.isPackageInstalled
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.isPackageInstalled
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.gpgFingerprint
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.web.base.downloadFromURL
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.web.base.downloadFromURL
|
||||||
|
|
||||||
|
|
||||||
fun Prov.installGopass(
|
fun Prov.installGopass(
|
||||||
version: String = "1.15.5",
|
version: String = "1.12.7",
|
||||||
enforceVersion: Boolean = false,
|
enforceVersion: Boolean = false,
|
||||||
sha256sum: String = "23ec10015c2643f22cb305859eb36d671094d463d2eb1798cc675e7bb06f4b39"
|
sha256sum: String = "0824d5110ff1e68bff1ba10c1be63acb67cb1ad8e3bccddd6b6fc989608beca8" // checksum for sha256sum version 8.30 (e.g. ubuntu 20.04)
|
||||||
) = taskWithResult {
|
) = taskWithResult {
|
||||||
|
|
||||||
if (isPackageInstalled("gopass") && !enforceVersion) {
|
if (isPackageInstalled("gopass") && !enforceVersion) {
|
||||||
|
@ -36,34 +34,29 @@ fun Prov.installGopass(
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
cmd("sudo dpkg -i $path/gopass_${version}_linux_amd64.deb")
|
cmd("sudo dpkg -i $path/gopass_${version}_linux_amd64.deb")
|
||||||
// Cross-check if installation was successful
|
// Cross-check if installation was successful
|
||||||
return@taskWithResult ProvResult(checkGopassVersion(version))
|
addResultToEval(ProvResult(checkGopassVersion(version)))
|
||||||
} else {
|
} else {
|
||||||
return@taskWithResult ProvResult(false, err = "Gopass could not be installed. " + result.err)
|
addResultToEval(ProvResult(false, err = "Gopass could not be installed. " + result.err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun Prov.configureGopass(gopassRootFolder: String? = null, publicGpgKey: Secret? = null) = taskWithResult {
|
fun Prov.configureGopass(gopassRootFolder: String? = null) = taskWithResult() {
|
||||||
|
val configFile = ".config/gopass/config.yml"
|
||||||
|
val defaultRootFolder = userHome() + ".password-store"
|
||||||
|
val rootFolder = gopassRootFolder ?: defaultRootFolder
|
||||||
|
|
||||||
val configFile = ".config/gopass/config"
|
if (checkFile(configFile)) {
|
||||||
|
|
||||||
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")
|
return@taskWithResult ProvResult(true, out = "Gopass already configured in file $configFile")
|
||||||
}
|
}
|
||||||
|
|
||||||
val defaultRootFolder = userHome() + ".password-store"
|
if ((gopassRootFolder != null) && (!gopassRootFolder.startsWith("/"))) {
|
||||||
val gopassRoot = gopassRootFolder ?: defaultRootFolder
|
return@taskWithResult ProvResult(false, err = "Gopass cannot be initialized with a relative path or path starting with ~")
|
||||||
|
}
|
||||||
// initialize root store
|
// use default
|
||||||
val fingerprint = publicGpgKey?.let { gpgFingerprint(it.plain()) }
|
createDir(rootFolder)
|
||||||
gopassInitStoreFolder(gopassRoot, fingerprint)
|
|
||||||
|
|
||||||
createDirs(".config/gopass")
|
createDirs(".config/gopass")
|
||||||
createFile(configFile, gopassConfig(gopassRoot))
|
createFile(configFile, gopassConfig(rootFolder))
|
||||||
|
|
||||||
// auto-completion
|
// auto-completion
|
||||||
configureBashForUser()
|
configureBashForUser()
|
||||||
|
@ -71,41 +64,31 @@ fun Prov.configureGopass(gopassRootFolder: String? = null, publicGpgKey: Secret?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun Prov.gopassMountStore(storeName: String, path: String) = taskWithResult {
|
fun Prov.gopassMountStore(storeName: String, path: String) = task {
|
||||||
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")
|
cmd("gopass mounts add $storeName $path")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun Prov.gopassInitStoreFolder(path: String, gpgFingerprint: String? = null ) = task {
|
@Suppress("unused")
|
||||||
createFile("$path/.gpg-id", gpgFingerprint ?: "_replace_this_by_a_fingerprint_of_a_public_gpg_key_")
|
fun Prov.gopassInitStore(storeName: String, indexOfRecepientKey: Int = 0) = task {
|
||||||
if (!checkDir(".git", path)) {
|
cmd("printf \"$indexOfRecepientKey\\n\" | gopass init --store=$storeName")
|
||||||
cmd("git init", path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun gopassConfig(gopassRoot: String): String {
|
internal fun gopassConfig(gopassRoot: String): String {
|
||||||
return """
|
return """
|
||||||
[core]
|
autoclip: true
|
||||||
parsing = true
|
autoimport: true
|
||||||
exportkeys = true
|
cliptimeout: 45
|
||||||
autoclip = true
|
exportkeys: true
|
||||||
showsafecontent = false
|
nocolor: false
|
||||||
nopager = false
|
nopager: false
|
||||||
cliptimeout = 45
|
notifications: true
|
||||||
notifications = true
|
parsing: true
|
||||||
autoimport = true
|
path: $gopassRoot
|
||||||
[age]
|
safecontent: false
|
||||||
usekeychain = false
|
mounts: {}
|
||||||
[mounts]
|
""".trimIndent() + "\n"
|
||||||
path = $gopassRoot
|
|
||||||
"""
|
|
||||||
.trimIndent() + "\n"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,12 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.*
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.isPackageInstalled
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.isPackageInstalled
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.web.base.downloadFromURL
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.web.base.downloadFromURL
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
fun Prov.downloadGopassBridge() = task {
|
fun Prov.downloadGopassBridge() = task {
|
||||||
// Attention: when changing the version, you also need to change the number after /file/ in the download url below
|
val version = "0.9.0"
|
||||||
val filename = "gopass_bridge-0.9.0-fx.xpi"
|
val filename = "gopass_bridge-${version}-fx.xpi"
|
||||||
val downloadDir = "${userHome()}Downloads/"
|
val downloadDir = "${userHome()}Downloads/"
|
||||||
|
|
||||||
createDirs(downloadDir)
|
createDirs(downloadDir)
|
||||||
|
@ -18,14 +19,13 @@ fun Prov.downloadGopassBridge() = task {
|
||||||
"-L https://addons.mozilla.org/firefox/downloads/file/3630534/$filename",
|
"-L https://addons.mozilla.org/firefox/downloads/file/3630534/$filename",
|
||||||
downloadDir + filename
|
downloadDir + filename
|
||||||
)
|
)
|
||||||
// needs manual installation with: firefox Downloads/gopass_bridge-0.X.0-fx.xpi
|
// needs manual installation with: firefox Downloads/gopass_bridge-0.8.0-fx.xpi
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.installGopassJsonApi() = taskWithResult {
|
fun Prov.installGopassBridgeJsonApi() = task {
|
||||||
// see https://github.com/gopasspw/gopass-jsonapi
|
// see https://github.com/gopasspw/gopass-jsonapi
|
||||||
val sha256sum = "ec9976e39a468428ae2eb1e2e0b9ceccba7f60d66b8097e2425b0c07f4fed108"
|
val gopassJsonApiVersion = "1.11.1"
|
||||||
val gopassJsonApiVersion = "1.15.5"
|
val requiredGopassVersion = "1.14.4"
|
||||||
val requiredGopassVersion = "1.15.5"
|
|
||||||
val filename = "gopass-jsonapi_${gopassJsonApiVersion}_linux_amd64.deb"
|
val filename = "gopass-jsonapi_${gopassJsonApiVersion}_linux_amd64.deb"
|
||||||
val downloadUrl = "-L https://github.com/gopasspw/gopass-jsonapi/releases/download/v$gopassJsonApiVersion/$filename"
|
val downloadUrl = "-L https://github.com/gopasspw/gopass-jsonapi/releases/download/v$gopassJsonApiVersion/$filename"
|
||||||
val downloadDir = "${userHome()}Downloads"
|
val downloadDir = "${userHome()}Downloads"
|
||||||
|
@ -36,7 +36,7 @@ fun Prov.installGopassJsonApi() = taskWithResult {
|
||||||
if (checkGopassVersion(requiredGopassVersion)) {
|
if (checkGopassVersion(requiredGopassVersion)) {
|
||||||
aptInstall("git gnupg2") // required dependencies
|
aptInstall("git gnupg2") // required dependencies
|
||||||
createDir(downloadDir)
|
createDir(downloadDir)
|
||||||
downloadFromURL(downloadUrl, filename, downloadDir, sha256sum = sha256sum)
|
downloadFromURL(downloadUrl, filename, downloadDir)
|
||||||
cmd("dpkg -i $downloadDir/$filename", sudo = true)
|
cmd("dpkg -i $downloadDir/$filename", sudo = true)
|
||||||
} else {
|
} else {
|
||||||
ProvResult(
|
ProvResult(
|
||||||
|
@ -46,56 +46,51 @@ fun Prov.installGopassJsonApi() = taskWithResult {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
addResultToEval(
|
||||||
ProvResult(
|
ProvResult(
|
||||||
false,
|
false,
|
||||||
"gopass not initialized correctly. You can initialize gopass with: \"gopass init\""
|
"gopass not initialized correctly. You can initialize gopass with: \"gopass init\""
|
||||||
)
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (installedJsonApiVersion.startsWith("gopass-jsonapi version $gopassJsonApiVersion")) {
|
if (installedJsonApiVersion.startsWith("gopass-jsonapi version $gopassJsonApiVersion")) {
|
||||||
ProvResult(true, out = "Version $gopassJsonApiVersion of gopass-jsonapi is already installed")
|
addResultToEval(ProvResult(true, out = "Version $gopassJsonApiVersion of gopass-jsonapi is already installed"))
|
||||||
} else {
|
} else {
|
||||||
|
addResultToEval(
|
||||||
ProvResult(
|
ProvResult(
|
||||||
false,
|
false,
|
||||||
err = "gopass-jsonapi (version $gopassJsonApiVersion) cannot be installed as version $installedJsonApiVersion is already installed." +
|
err = "gopass-jsonapi (version $gopassJsonApiVersion) cannot be installed as version $installedJsonApiVersion is already installed." +
|
||||||
" Upgrading gopass-jsonapi is currently not supported by provs."
|
" Upgrading gopass-jsonapi is currently not supported by provs."
|
||||||
)
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun Prov.configureGopassWrapperShForFirefox() = task {
|
||||||
* Configures apparmor to allow firefox to access to gopass_wrapper.sh in avoid
|
|
||||||
* the error "An unexpected error occurred - Is your browser correctly set up for gopass? ..."
|
|
||||||
* when trying to use gopass bridge.
|
|
||||||
* This error appears in spite of having already set up gopass-jsonapi correctly.
|
|
||||||
*/
|
|
||||||
fun Prov.configureApparmorForGopassWrapperShForFirefox() = task {
|
|
||||||
|
|
||||||
val appArmorFile = "/etc/apparmor.d/usr.bin.firefox"
|
val appArmorFile = "/etc/apparmor.d/usr.bin.firefox"
|
||||||
val gopassAccessPermission = "owner @{HOME}/.config/gopass/gopass_wrapper.sh Ux,"
|
|
||||||
val insertAfterText = "# per-user firefox configuration\n"
|
|
||||||
|
|
||||||
if (checkFile(appArmorFile) && !fileContainsText(appArmorFile, gopassAccessPermission, true)) {
|
if (checkFile(appArmorFile)) {
|
||||||
replaceTextInFile(
|
addTextToFile(
|
||||||
appArmorFile, insertAfterText, "$insertAfterText $gopassAccessPermission\n"
|
"\nowner @{HOME}/.config/gopass/gopass_wrapper.sh Ux\n",
|
||||||
|
File(appArmorFile),
|
||||||
|
sudo = true
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
cmd("systemctl reload apparmor", sudo = true)
|
cmd("systemctl reload apparmor", sudo = true)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun Prov.configureGopassJsonApi() = taskWithResult {
|
fun Prov.configureGopassBridgeJsonApi() = task {
|
||||||
if (isPackageInstalled("gopass-jsonapi")) {
|
if (isPackageInstalled("gopass-jsonapi")) {
|
||||||
// configures gopass-jsonapi for firefox and chooses default for each:
|
// configure for firefox and choose default for each:
|
||||||
// * "Install for all users? [y/N/q]",
|
// "Install for all users? [y/N/q]",
|
||||||
// * "In which path should gopass_wrapper.sh be installed? [/home/<user>/.config/gopass]"
|
// "In which path should gopass_wrapper.sh be installed? [/home/testuser/.config/gopass]"
|
||||||
// * "Wrapper Script for gopass_wrapper.sh ..."
|
// "Wrapper Script for gopass_wrapper.sh ..."
|
||||||
//
|
configureGopassWrapperShForFirefox()
|
||||||
// I.e. creates file "gopass_wrapper.sh" in "/home/<user>/.config/gopass" as well as
|
|
||||||
// the manifest file "/home/<user>/.mozilla/native-messaging-hosts/com.justwatch.gopass.json"
|
|
||||||
cmd("printf \"\\n\\n\\n\" | gopass-jsonapi configure --browser firefox")
|
cmd("printf \"\\n\\n\\n\" | gopass-jsonapi configure --browser firefox")
|
||||||
|
|
||||||
configureApparmorForGopassWrapperShForFirefox()
|
|
||||||
} else {
|
} else {
|
||||||
ProvResult(
|
ProvResult(
|
||||||
false,
|
false,
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
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")
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ val NETWORK_TOOLS = "curl wget net-tools"
|
||||||
|
|
||||||
val KEY_MANAGEMENT_GUI = "seahorse"
|
val KEY_MANAGEMENT_GUI = "seahorse"
|
||||||
|
|
||||||
val BROWSER = "chromium-browser" // firefox can be installed by installFirefox
|
val BROWSER = "firefox chromium-browser"
|
||||||
|
|
||||||
val EMAIL_CLIENT = "thunderbird"
|
val EMAIL_CLIENT = "thunderbird"
|
||||||
|
|
||||||
|
@ -39,5 +39,3 @@ val CLOJURE_TOOLS = "leiningen"
|
||||||
val PASSWORD_TOOLS = "pwgen"
|
val PASSWORD_TOOLS = "pwgen"
|
||||||
|
|
||||||
val SCREEN_TOOLS = "scrcpy"
|
val SCREEN_TOOLS = "scrcpy"
|
||||||
|
|
||||||
val COMPARE_TOOLS = "meld"
|
|
||||||
|
|
|
@ -7,53 +7,35 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInsta
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
fun Prov.provisionPython(venvHome: String? = "~/.venv/meissa") = task {
|
fun Prov.provisionPython() = task {
|
||||||
installPython3()
|
installPython3()
|
||||||
if (venvHome != null) { configureVenv(venvHome) }
|
configureVenv()
|
||||||
installPybuilder(venvHome)
|
installPybuilder()
|
||||||
installRestClient(venvHome)
|
installRestClient()
|
||||||
installJupyterlab(venvHome)
|
installJupyterlab()
|
||||||
installLinters(venvHome)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.installPython3(): ProvResult = task {
|
fun Prov.installPython3(): ProvResult = task {
|
||||||
aptInstall("python3-venv python3-pip")
|
aptInstall("python3-venv python3-pip")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.configureVenv(venvHome: String): ProvResult = task {
|
fun Prov.configureVenv(): ProvResult = task {
|
||||||
cmd("python3 -m venv $venvHome")
|
val venvHome = "~/.venv/meissa"
|
||||||
createSymlink(File("$venvHome/bin/activate"), File("~/.bashrc.d/venv.sh"))
|
cmd("python3 -m venv " + venvHome)
|
||||||
pipInstall("pip --upgrade", venvHome)
|
cmd("source " + venvHome + "/bin/activate")
|
||||||
|
createSymlink(File(venvHome + "/bin/activate"), File("~/.bashrc.d/venv.sh"))
|
||||||
|
cmd("pip3 install pip --upgrade")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.installPybuilder(venvHome: String? = null): ProvResult = task {
|
fun Prov.installPybuilder(): ProvResult = task {
|
||||||
pipInstall("pybuilder ddadevops pypandoc mockito coverage unittest-xml-reporting deprecation" +
|
cmd("pip3 install pybuilder ddadevops pypandoc mockito coverage unittest-xml-reporting deprecation python_terraform " +
|
||||||
" python_terraform dda_python_terraform boto3 pyyaml packaging",
|
"boto3")
|
||||||
venvHome
|
|
||||||
)
|
|
||||||
pipInstall("--upgrade ddadevops", venvHome)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.installRestClient(venvHome: String? = null): ProvResult = task {
|
fun Prov.installRestClient(): ProvResult = task {
|
||||||
pipInstall("requests", venvHome)
|
cmd("pip3 install requests")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.installJupyterlab(venvHome: String? = null): ProvResult = task {
|
fun Prov.installJupyterlab(): ProvResult = task {
|
||||||
pipInstall("jupyterlab pandas matplotlib", venvHome)
|
cmd("pip3 install jupyterlab pandas matplotlib")
|
||||||
}
|
|
||||||
|
|
||||||
fun Prov.installLinters(venvHome: String? = null): ProvResult = task {
|
|
||||||
pipInstall("flake8 mypy pylint", 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 && "
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,9 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.isPackag
|
||||||
|
|
||||||
|
|
||||||
fun Prov.installVSC(vararg options: String) = task {
|
fun Prov.installVSC(vararg options: String) = task {
|
||||||
val clojureExtensions = setOf("betterthantomorrow.calva", "DavidAnson.vscode-markdownlint")
|
val clojureExtensions =
|
||||||
val pythonExtensions = setOf("ms-python.python")
|
arrayListOf("betterthantomorrow.calva", "martinklepsch.clojure-joker-linter", "DavidAnson.vscode-markdownlint")
|
||||||
|
val pythonExtensions = arrayListOf("ms-python.python")
|
||||||
|
|
||||||
prerequisitesVSCinstall()
|
prerequisitesVSCinstall()
|
||||||
|
|
||||||
|
@ -23,6 +24,8 @@ fun Prov.installVSC(vararg options: String) = task {
|
||||||
installExtensionsCode(pythonExtensions)
|
installExtensionsCode(pythonExtensions)
|
||||||
installExtensionsCodium(pythonExtensions)
|
installExtensionsCodium(pythonExtensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
provisionAdditionalToolsForVSCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,7 +65,7 @@ private fun Prov.installVSCodiumPackage() = task {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun Prov.installExtensionsCode(extensions: Set<String>) = optional {
|
private fun Prov.installExtensionsCode(extensions: List<String>) = optional {
|
||||||
var res = ProvResult(true)
|
var res = ProvResult(true)
|
||||||
for (ext in extensions) {
|
for (ext in extensions) {
|
||||||
res = cmd("code --install-extension $ext")
|
res = cmd("code --install-extension $ext")
|
||||||
|
@ -71,7 +74,7 @@ private fun Prov.installExtensionsCode(extensions: Set<String>) = optional {
|
||||||
// Settings can be found at $HOME/.config/Code/User/settings.json
|
// Settings can be found at $HOME/.config/Code/User/settings.json
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Prov.installExtensionsCodium(extensions: Set<String>) = optional {
|
private fun Prov.installExtensionsCodium(extensions: List<String>) = optional {
|
||||||
var res = ProvResult(true)
|
var res = ProvResult(true)
|
||||||
for (ext in extensions) {
|
for (ext in extensions) {
|
||||||
res = cmd("codium --install-extension $ext")
|
res = cmd("codium --install-extension $ext")
|
||||||
|
@ -79,3 +82,12 @@ private fun Prov.installExtensionsCodium(extensions: Set<String>) = optional {
|
||||||
res
|
res
|
||||||
// Settings can be found at $HOME/.config/Code/User/settings.json
|
// 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/")
|
||||||
|
}
|
||||||
|
|
|
@ -68,23 +68,11 @@ open class Prov protected constructor(
|
||||||
private val internalResults = arrayListOf<ResultLine>()
|
private val internalResults = arrayListOf<ResultLine>()
|
||||||
private val infoTexts = arrayListOf<String>()
|
private val infoTexts = arrayListOf<String>()
|
||||||
|
|
||||||
/**
|
|
||||||
* A session is the top-level execution unit in provs. A session can contain tasks.
|
|
||||||
* Returns success if no sub-tasks are called or if all subtasks finish with success.
|
|
||||||
*/
|
|
||||||
fun session(taskLambda: Prov.() -> ProvResult): ProvResult {
|
|
||||||
if (level > 0) {
|
|
||||||
throw IllegalStateException("A session can only be created on the top-level and may not be included in another session or task.")
|
|
||||||
}
|
|
||||||
return evaluate(ResultMode.ALL, "session") { taskLambda() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A task is the base execution unit in provs. In the results overview it is represented by one line resp. result (of either success or failure).
|
* A task is the 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.
|
* Returns success if no sub-tasks are called or if all subtasks finish with success.
|
||||||
*/
|
*/
|
||||||
fun task(name: String? = null, taskLambda: Prov.() -> Unit): ProvResult {
|
fun task(name: String? = null, taskLambda: Prov.() -> Unit): ProvResult {
|
||||||
printDeprecationWarningIfLevel0("task")
|
|
||||||
return evaluate(ResultMode.ALL, name) { taskLambda(); ProvResult(true) }
|
return evaluate(ResultMode.ALL, name) { taskLambda(); ProvResult(true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,31 +81,27 @@ open class Prov protected constructor(
|
||||||
* The returned result is included in the evaluation.
|
* The returned result is included in the evaluation.
|
||||||
*/
|
*/
|
||||||
fun taskWithResult(name: String? = null, taskLambda: Prov.() -> ProvResult): ProvResult {
|
fun taskWithResult(name: String? = null, taskLambda: Prov.() -> ProvResult): ProvResult {
|
||||||
printDeprecationWarningIfLevel0("taskWithResult")
|
|
||||||
return evaluate(ResultMode.ALL, name) { taskLambda() }
|
return evaluate(ResultMode.ALL, name) { taskLambda() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* defines a task, which returns the returned result, the results of sub-tasks are not considered
|
* defines a task, which returns the returned result, the results of sub-tasks are not considered
|
||||||
*/
|
*/
|
||||||
fun requireLast(name: String? = null, a: Prov.() -> ProvResult): ProvResult {
|
fun requireLast(a: Prov.() -> ProvResult): ProvResult {
|
||||||
printDeprecationWarningIfLevel0("requireLast")
|
return evaluate(ResultMode.LAST) { a() }
|
||||||
return evaluate(ResultMode.LAST, name) { a() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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(a: Prov.() -> ProvResult): ProvResult {
|
||||||
printDeprecationWarningIfLevel0("optional")
|
return evaluate(ResultMode.OPTIONAL) { a() }
|
||||||
return evaluate(ResultMode.OPTIONAL, name) { a() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* defines a task, which exits the overall execution on failure
|
* defines a task, which exits the overall execution on failure
|
||||||
*/
|
*/
|
||||||
fun exitOnFailure(a: Prov.() -> ProvResult): ProvResult {
|
fun exitOnFailure(a: Prov.() -> ProvResult): ProvResult {
|
||||||
printDeprecationWarningIfLevel0("exitOnFailure")
|
|
||||||
return evaluate(ResultMode.FAILEXIT) { a() }
|
return evaluate(ResultMode.FAILEXIT) { a() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +109,6 @@ open class Prov protected constructor(
|
||||||
* Runs the provided task in the specified (running) container
|
* Runs the provided task in the specified (running) container
|
||||||
*/
|
*/
|
||||||
fun taskInContainer(containerName: String, taskLambda: Prov.() -> ProvResult): ProvResult {
|
fun taskInContainer(containerName: String, taskLambda: Prov.() -> ProvResult): ProvResult {
|
||||||
printDeprecationWarningIfLevel0("taskInContainer")
|
|
||||||
runInContainerWithName = containerName
|
runInContainerWithName = containerName
|
||||||
val res = evaluate(ResultMode.ALL) { taskLambda() }
|
val res = evaluate(ResultMode.ALL) { taskLambda() }
|
||||||
runInContainerWithName = null
|
runInContainerWithName = null
|
||||||
|
@ -271,8 +254,6 @@ open class Prov protected constructor(
|
||||||
previousLevel = -1
|
previousLevel = -1
|
||||||
exit = false
|
exit = false
|
||||||
initProgress()
|
initProgress()
|
||||||
|
|
||||||
processor.open()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pre-handling
|
// pre-handling
|
||||||
|
@ -331,15 +312,6 @@ open class Prov protected constructor(
|
||||||
|
|
||||||
internalResults[resultIndex].provResult = returnValue
|
internalResults[resultIndex].provResult = returnValue
|
||||||
|
|
||||||
// Add failure result to output if not yet included,
|
|
||||||
// which is the case if the result was not part of another subtask but created and returned by the lambda itself.
|
|
||||||
// Success results do not need to be added here as they don't change the overall success evaluation,
|
|
||||||
// whereas the failure results may have a useful error message, which should be in the output.
|
|
||||||
// Only direct result objects are added, but not result objects that were passed from a subtask as they are already handled in the subtask.
|
|
||||||
if (!resultOfTaskLambda.success && (resultIndex < internalResults.size - 1) && (resultOfTaskLambda != internalResults[resultIndex + 1].provResult)) {
|
|
||||||
internalResults.add(ResultLine(level + 1, "<<returned result>>", resultOfTaskLambda))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (level == 0) {
|
if (level == 0) {
|
||||||
endProgress()
|
endProgress()
|
||||||
processor.close()
|
processor.close()
|
||||||
|
@ -350,12 +322,8 @@ open class Prov protected constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the task at the specified index has no subtasks.
|
|
||||||
* I.e. if the task is the last one or if level of the next task is the same or less (which means same level or "higher" in the tree)
|
|
||||||
*/
|
|
||||||
private fun internalResultIsLeaf(resultIndex: Int): Boolean {
|
private fun internalResultIsLeaf(resultIndex: Int): Boolean {
|
||||||
return (resultIndex >= internalResults.size - 1 || internalResults[resultIndex].level >= internalResults[resultIndex + 1].level)
|
return !(resultIndex < internalResults.size - 1 && internalResults[resultIndex + 1].level > internalResults[resultIndex].level)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -464,11 +432,6 @@ open class Prov protected constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun printDeprecationWarningIfLevel0(methodName: String) {
|
|
||||||
if (level == 0 && progressType != ProgressType.NONE) {
|
|
||||||
println("WARNING: method $methodName should not be used at top-level, use method <session> instead.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ internal fun getCallingMethodName(): String? {
|
||||||
val offsetVal = 1
|
val offsetVal = 1
|
||||||
val exclude = arrayOf("task", "task\$default", "taskWithResult\$default", "taskWithResult", "def", "def\$default", "record", "invoke", "invoke0", "evaluate", "evaluate\$default", )
|
val exclude = arrayOf("task", "task\$default", "taskWithResult\$default", "taskWithResult", "def", "def\$default", "record", "invoke", "invoke0", "evaluate", "evaluate\$default", )
|
||||||
// suffixes are also ignored as method names but will be added as suffix in the evaluation results
|
// suffixes are also ignored as method names but will be added as suffix in the evaluation results
|
||||||
val suffixes = arrayOf("optional", "optional\$default", "requireAll", "requireLast", "requireLast\$default", "inContainer")
|
val suffixes = arrayOf("optional", "requireAll", "requireLast", "inContainer")
|
||||||
|
|
||||||
var suffix = ""
|
var suffix = ""
|
||||||
val callingFrame = Thread.currentThread().stackTrace
|
val callingFrame = Thread.currentThread().stackTrace
|
||||||
|
@ -30,7 +30,7 @@ internal fun getCallingMethodName(): String? {
|
||||||
var inc = 0
|
var inc = 0
|
||||||
while ((method in exclude) or (method in suffixes)) {
|
while ((method in exclude) or (method in suffixes)) {
|
||||||
if (method in suffixes && suffix == "") {
|
if (method in suffixes && suffix == "") {
|
||||||
suffix = method.split("$")[0]
|
suffix = method
|
||||||
}
|
}
|
||||||
inc++
|
inc++
|
||||||
method = callingFrame[i + offsetVal + inc].methodName
|
method = callingFrame[i + offsetVal + inc].methodName
|
||||||
|
|
|
@ -6,57 +6,88 @@ import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.local
|
import org.domaindrivenarchitecture.provs.framework.core.local
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.remote
|
import org.domaindrivenarchitecture.provs.framework.core.remote
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.PromptSecretSource
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.PromptSecretSource
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.currentUserCanSudo
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.makeUserSudoerWithNoSudoPasswordRequired
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.whoami
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Prov instance according to the targetCommand.
|
* Returns a Prov instance according to the targetCommand.
|
||||||
* Returns a local Prov instance if targetCommand.isValidLocalhost() is true resp.
|
* E.g. it returns a local Prov instance if targetCommand.isValidLocalhost() is true or
|
||||||
* returns a remote Prov instance if targetCommand.isValidRemote() is true.
|
* returns a remote Prov instance if targetCommand.isValidRemote() is true.
|
||||||
|
*
|
||||||
|
* If the target is remote and if parameter remoteHostSetSudoWithoutPasswordRequired is set to true,
|
||||||
|
* it will enable sudo without password on the remote machine (in case this was not yet enabled).
|
||||||
*/
|
*/
|
||||||
fun createProvInstance(targetCommand: TargetCliCommand): Prov {
|
fun createProvInstance(
|
||||||
|
targetCommand: TargetCliCommand,
|
||||||
|
remoteHostSetSudoWithoutPasswordRequired: Boolean = false
|
||||||
|
): Prov {
|
||||||
if (targetCommand.isValid()) {
|
if (targetCommand.isValid()) {
|
||||||
val password: Secret? = targetCommand.remoteTarget()?.password
|
val password: Secret? = targetCommand.remoteTarget()?.password
|
||||||
|
|
||||||
return if (targetCommand.isValidLocalhost()) {
|
val remoteTarget = targetCommand.remoteTarget()
|
||||||
local()
|
if (targetCommand.isValidLocalhost()) {
|
||||||
} else if (targetCommand.isValidRemote()) {
|
return local()
|
||||||
createRemoteProvInstance(targetCommand.remoteTarget(), password)
|
} else if (targetCommand.isValidRemote() && remoteTarget != null) {
|
||||||
} else {
|
return createProvInstanceRemote(
|
||||||
throw IllegalArgumentException(
|
remoteTarget.host,
|
||||||
"Error: neither a valid localHost nor a valid remoteHost was specified! Use option -h for help."
|
remoteTarget.user,
|
||||||
|
remoteTarget.password == null,
|
||||||
|
password,
|
||||||
|
remoteHostSetSudoWithoutPasswordRequired
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException("Error: neither a valid localHost nor a valid remoteHost was specified! Use option -h for help.")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println("ERROR: Invalid target (${targetCommand.target}). Please use option -h for help.")
|
println("Invalid command line options.\nPlease use option -h for help.")
|
||||||
System.out.flush()
|
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createProvInstanceRemote(
|
||||||
internal fun createRemoteProvInstance(
|
host: String,
|
||||||
target: TargetCliCommand.RemoteTarget?,
|
remoteUser: String,
|
||||||
password: Secret? = null
|
sshWithKey: Boolean,
|
||||||
|
password: Secret?,
|
||||||
|
remoteHostSetSudoWithoutPasswordRequired: Boolean
|
||||||
): Prov {
|
): Prov {
|
||||||
return if (target != null) {
|
val prov =
|
||||||
remote(target.host, target.user, target.password ?: password)
|
if (sshWithKey) {
|
||||||
|
remote(host, remoteUser)
|
||||||
} else {
|
} else {
|
||||||
throw IllegalArgumentException(
|
require(
|
||||||
"Error: no valid remote target (host & user) was specified!"
|
password != null,
|
||||||
)
|
{ "No password available for provisioning without ssh keys. Either specify provisioning by ssh-keys or provide password." })
|
||||||
|
remote(host, remoteUser, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!prov.currentUserCanSudo()) {
|
||||||
|
if (remoteHostSetSudoWithoutPasswordRequired) {
|
||||||
|
require(
|
||||||
|
password != null,
|
||||||
|
{ "User ${prov.whoami()} not able to sudo on remote machine without password and no password available for the user." })
|
||||||
|
prov.makeUserSudoerWithNoSudoPasswordRequired(password)
|
||||||
|
|
||||||
|
// a new session is required after making the user a sudoer without password
|
||||||
|
return remote(host, remoteUser, password)
|
||||||
|
} else {
|
||||||
|
throw IllegalStateException("User ${prov.whoami()} not able to sudo on remote machine without password and option not set to enable user to sudo without password.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return prov
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun getPasswordToConfigureSudoWithoutPassword(): Secret {
|
// todo: consider removal as password can be retrieved by PromptSecretSource
|
||||||
return PromptSecretSource("password to configure sudo without password.").secret()
|
internal fun retrievePassword(cliCommand: TargetCliCommand): Secret? {
|
||||||
}
|
var password: Secret? = null
|
||||||
|
if (cliCommand.isValidRemote() && cliCommand.passwordInteractive) {
|
||||||
|
password =
|
||||||
|
PromptSecretSource("Password for user $cliCommand.userName!! on $cliCommand.remoteHost!!").secret()
|
||||||
|
|
||||||
|
}
|
||||||
/**
|
return password
|
||||||
* Wrapper for exitProcess, which allows e.g. mocking for test purposes
|
|
||||||
*/
|
|
||||||
fun quit(status: Int): Nothing {
|
|
||||||
exitProcess(status)
|
|
||||||
}
|
}
|
|
@ -13,6 +13,13 @@ class UbuntuProv internal constructor(
|
||||||
progressType: ProgressType
|
progressType: ProgressType
|
||||||
) : Prov(processor, name, progressType) {
|
) : Prov(processor, name, progressType) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
val user = cmdNoLog("whoami").out?.trim()
|
||||||
|
if ("root" != user && !cmdNoLog("timeout 1 sudo id").success) {
|
||||||
|
println("IMPORTANT INFO:\nUser $user cannot sudo without entering a password, i.e. some functions may fail!\nIf you need to run functions with sudo, please ensure $user can sudo without password.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun cmd(cmd: String, dir: String?, sudo: Boolean): ProvResult = taskWithResult {
|
override fun cmd(cmd: String, dir: String?, sudo: Boolean): ProvResult = taskWithResult {
|
||||||
exec(SHELL, "-c", commandWithDirAndSudo(cmd, dir, sudo))
|
exec(SHELL, "-c", commandWithDirAndSudo(cmd, dir, sudo))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.docker.provideContainer
|
import org.domaindrivenarchitecture.provs.framework.core.docker.provideContainer
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.escapeAndEncloseByDoubleQuoteForShell
|
import org.domaindrivenarchitecture.provs.framework.core.escapeAndEncloseByDoubleQuoteForShell
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.platforms.SHELL
|
import org.domaindrivenarchitecture.provs.framework.core.platforms.SHELL
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.tags.Api
|
||||||
|
|
||||||
enum class ContainerStartMode {
|
enum class ContainerStartMode {
|
||||||
USE_RUNNING_ELSE_CREATE,
|
USE_RUNNING_ELSE_CREATE,
|
||||||
|
@ -19,24 +20,26 @@ enum class ContainerEndMode {
|
||||||
|
|
||||||
open class ContainerUbuntuHostProcessor(
|
open class ContainerUbuntuHostProcessor(
|
||||||
private val containerName: String = "default_provs_container",
|
private val containerName: String = "default_provs_container",
|
||||||
dockerImage: String = "ubuntu",
|
@Api // suppress false positive warning
|
||||||
startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
private val dockerImage: String = "ubuntu",
|
||||||
|
@Api // suppress false positive warning
|
||||||
|
private val startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
||||||
private val endMode: ContainerEndMode = ContainerEndMode.KEEP_RUNNING,
|
private val endMode: ContainerEndMode = ContainerEndMode.KEEP_RUNNING,
|
||||||
sudo: Boolean = true,
|
@Api // suppress false positive warning
|
||||||
options: String = ""
|
private val sudo: Boolean = true
|
||||||
) : Processor {
|
) : Processor {
|
||||||
|
|
||||||
private val hostShell = "/bin/bash"
|
|
||||||
private val dockerCmd = if (sudo) "sudo docker " else "docker "
|
private val dockerCmd = if (sudo) "sudo docker " else "docker "
|
||||||
private var localExecution = LocalProcessor()
|
private var localExecution = LocalProcessor()
|
||||||
private var a = Prov.newInstance(name = "LocalProcessor for Docker operations", progressType = ProgressType.NONE)
|
private var a = Prov.newInstance(name = "LocalProcessor for Docker operations", progressType = ProgressType.NONE)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val result = a.provideContainer(containerName, dockerImage, startMode, sudo, options)
|
val r = a.provideContainer(containerName, dockerImage, startMode, sudo)
|
||||||
if (!result.success)
|
if (!r.success)
|
||||||
throw RuntimeException("Could not start docker image: " + result.toString(), result.exception)
|
throw RuntimeException("Could not start docker image: " + r.toString(), r.exception)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val hostShell = "/bin/bash"
|
||||||
|
|
||||||
override fun exec(vararg args: String): ProcessResult {
|
override fun exec(vararg args: String): ProcessResult {
|
||||||
return localExecution.exec(hostShell, "-c", dockerCmd + "exec $containerName " + buildCommand(*args))
|
return localExecution.exec(hostShell, "-c", dockerCmd + "exec $containerName " + buildCommand(*args))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,10 @@ package org.domaindrivenarchitecture.provs.framework.core.processors
|
||||||
|
|
||||||
|
|
||||||
interface Processor {
|
interface Processor {
|
||||||
fun open() {
|
|
||||||
// no action needed for most processors; otherwise, overwrite this method in the implementing class
|
|
||||||
}
|
|
||||||
fun exec(vararg args: String): ProcessResult
|
fun exec(vararg args: String): ProcessResult
|
||||||
fun execNoLog(vararg args: String): ProcessResult
|
fun execNoLog(vararg args: String): ProcessResult
|
||||||
fun close() {
|
fun close() {
|
||||||
// no action needed for most processors; otherwise, overwrite this method in the implementing class
|
// no action needed for most processors; if action is needed when closing, this method must be overwritten in the subclass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,28 +21,23 @@ import java.util.concurrent.TimeUnit
|
||||||
* Executes task on a remote machine.
|
* Executes task on a remote machine.
|
||||||
* Attention: host key is currently not being verified
|
* Attention: host key is currently not being verified
|
||||||
*/
|
*/
|
||||||
class RemoteProcessor(val host: InetAddress, val user: String, val password: Secret? = null) : Processor {
|
class RemoteProcessor(host: InetAddress, user: String, password: Secret? = null) : Processor {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@Suppress("JAVA_CLASS_ON_COMPANION")
|
@Suppress("JAVA_CLASS_ON_COMPANION")
|
||||||
private val log = LoggerFactory.getLogger(javaClass.enclosingClass)
|
private val log = LoggerFactory.getLogger(javaClass.enclosingClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var ssh = SSHClient()
|
private val ssh = SSHClient()
|
||||||
|
|
||||||
override fun open() {
|
init {
|
||||||
try {
|
try {
|
||||||
// always create a new instance as old one might be closed
|
|
||||||
ssh = SSHClient()
|
|
||||||
|
|
||||||
log.info("Connecting to $host with user: $user with " + if (password != null) "password" else "ssh-key")
|
log.info("Connecting to $host with user: $user with " + if (password != null) "password" else "ssh-key")
|
||||||
|
|
||||||
ssh.loadKnownHosts()
|
ssh.loadKnownHosts()
|
||||||
|
|
||||||
// Attention: host key is not verified
|
// Attention: host key is not verified
|
||||||
ssh.addHostKeyVerifier(PromiscuousVerifier())
|
ssh.addHostKeyVerifier(PromiscuousVerifier())
|
||||||
|
|
||||||
ssh.connectTimeout = 30000 // ms
|
|
||||||
ssh.connect(host)
|
ssh.connect(host)
|
||||||
|
|
||||||
if (password != null) {
|
if (password != null) {
|
||||||
|
@ -55,9 +50,8 @@ class RemoteProcessor(val host: InetAddress, val user: String, val password: Sec
|
||||||
try {
|
try {
|
||||||
ssh.disconnect()
|
ssh.disconnect()
|
||||||
} finally {
|
} finally {
|
||||||
val errorMag = "Error when initializing ssh (Host, username, password or ssh-key might be wrong) "
|
log.error("Got exception when initializing ssh (Username, password or ssh-key might be wrong): " + e.message)
|
||||||
log.error(errorMag + e.message)
|
throw RuntimeException("Error when initializing ssh (Username, password or ssh-key might be wrong) ", e)
|
||||||
throw RuntimeException(errorMag, e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -251,7 +251,7 @@ fun Prov.replaceTextInFile(file: String, oldText: String, replacement: String) =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun Prov.replaceTextInFile(file: String, oldText: Regex, replacement: String) = taskWithResult {
|
fun Prov.replaceTextInFile(file: String, oldText: Regex, replacement: String) = task {
|
||||||
// todo: only use sudo for root or if owner different from current
|
// todo: only use sudo for root or if owner different from current
|
||||||
val content = fileContent(file, true)
|
val content = fileContent(file, true)
|
||||||
if (content != null) {
|
if (content != null) {
|
||||||
|
|
|
@ -45,7 +45,8 @@ fun Prov.gitClone(
|
||||||
fun Prov.trustGithub() = task {
|
fun Prov.trustGithub() = task {
|
||||||
// current fingerprints from https://docs.github.com/en/github/authenticating-to-github/githubs-ssh-key-fingerprints
|
// current fingerprints from https://docs.github.com/en/github/authenticating-to-github/githubs-ssh-key-fingerprints
|
||||||
val fingerprints = setOf(
|
val fingerprints = setOf(
|
||||||
"SHA256:uNiVztksCsDhcc0u9e8BujQXVUpKZIDTMczCvj3tD2s github.com", // (RSA)
|
"SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com", // (RSA)
|
||||||
|
// supported beginning September 14, 2021:
|
||||||
"SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM github.com", // (ECDSA)
|
"SHA256:p2QAMXNIC1TJYWeIOttrVc98/R1BUFWu3/LiyKgUfQM github.com", // (ECDSA)
|
||||||
"SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU github.com" // (Ed25519)
|
"SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU github.com" // (Ed25519)
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,10 +20,7 @@ fun Prov.aptInstall(packages: String, ignoreAlreadyInstalled: Boolean = true): P
|
||||||
if (!allInstalled) {
|
if (!allInstalled) {
|
||||||
if (!isPackageInstalled(packages)) {
|
if (!isPackageInstalled(packages)) {
|
||||||
if (!aptInit) {
|
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 update")
|
||||||
}
|
|
||||||
cmd("sudo apt-get install -qy apt-utils")
|
cmd("sudo apt-get install -qy apt-utils")
|
||||||
aptInit = true
|
aptInit = true
|
||||||
}
|
}
|
||||||
|
@ -76,15 +73,7 @@ fun Prov.isPackageInstalled(packageName: String): Boolean {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if a package is installed else false
|
* Removes a package including its configuration and data files
|
||||||
*/
|
|
||||||
fun Prov.checkPackageInstalled(packageName: String): ProvResult = taskWithResult {
|
|
||||||
cmd("dpkg -s $packageName")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a package including its configuration and data file
|
|
||||||
*/
|
*/
|
||||||
@Suppress("unused") // used externally
|
@Suppress("unused") // used externally
|
||||||
fun Prov.aptPurge(packageName: String): Boolean {
|
fun Prov.aptPurge(packageName: String): Boolean {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources
|
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.Prov
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
||||||
|
@ -12,12 +11,12 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
||||||
class FileSecretSource(fqFileName: String) : SecretSource(fqFileName) {
|
class FileSecretSource(fqFileName: String) : SecretSource(fqFileName) {
|
||||||
|
|
||||||
override fun secret(): Secret {
|
override fun secret(): Secret {
|
||||||
val p = Prov.newInstance(name = "FileSecretSource", progressType = ProgressType.NONE)
|
val p = Prov.newInstance(name = "FileSecretSource")
|
||||||
return p.getSecret("cat " + input) ?: throw Exception("Failed to get secret.")
|
return p.getSecret("cat " + input) ?: throw Exception("Failed to get secret.")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun secretNullable(): Secret? {
|
override fun secretNullable(): Secret? {
|
||||||
val p = Prov.newInstance(name = "FileSecretSource", progressType = ProgressType.NONE)
|
val p = Prov.newInstance(name = "FileSecretSource")
|
||||||
return p.getSecret("cat " + input)
|
return p.getSecret("cat " + input)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources
|
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.Prov
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
||||||
|
@ -14,7 +13,7 @@ class GopassSecretSource(path: String) : SecretSource(path) {
|
||||||
return secretNullable() ?: throw Exception("Failed to get \"$input\" secret from gopass.")
|
return secretNullable() ?: throw Exception("Failed to get \"$input\" secret from gopass.")
|
||||||
}
|
}
|
||||||
override fun secretNullable(): Secret? {
|
override fun secretNullable(): Secret? {
|
||||||
val p = Prov.newInstance(name = "GopassSecretSource for $input", progressType = ProgressType.NONE)
|
val p = Prov.newInstance(name = "GopassSecretSource for $input")
|
||||||
return p.getSecret("gopass show -f $input", true)
|
return p.getSecret("gopass show -f $input", true)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources
|
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.Prov
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
||||||
|
@ -11,11 +10,11 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSource
|
||||||
*/
|
*/
|
||||||
class PassSecretSource(path: String) : SecretSource(path) {
|
class PassSecretSource(path: String) : SecretSource(path) {
|
||||||
override fun secret(): Secret {
|
override fun secret(): Secret {
|
||||||
val p = Prov.newInstance(name = "PassSecretSource", progressType = ProgressType.NONE)
|
val p = Prov.newInstance(name = "PassSecretSource")
|
||||||
return p.getSecret("pass " + input) ?: throw Exception("Failed to get secret.")
|
return p.getSecret("pass " + input) ?: throw Exception("Failed to get secret.")
|
||||||
}
|
}
|
||||||
override fun secretNullable(): Secret? {
|
override fun secretNullable(): Secret? {
|
||||||
val p = Prov.newInstance(name = "PassSecretSource", progressType = ProgressType.NONE)
|
val p = Prov.newInstance(name = "PassSecretSource")
|
||||||
return p.getSecret("pass " + input)
|
return p.getSecret("pass " + input)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,7 +8,7 @@ import javax.swing.*
|
||||||
|
|
||||||
class PasswordPanel : JPanel(FlowLayout()) {
|
class PasswordPanel : JPanel(FlowLayout()) {
|
||||||
|
|
||||||
private val passwordField = JPasswordField(30)
|
private val passwordField = JPasswordField(20)
|
||||||
private var entered = false
|
private var entered = false
|
||||||
|
|
||||||
val enteredPassword
|
val enteredPassword
|
||||||
|
|
|
@ -25,15 +25,15 @@ fun Prov.userExists(userName: String): Boolean {
|
||||||
fun Prov.createUser(
|
fun Prov.createUser(
|
||||||
userName: String,
|
userName: String,
|
||||||
password: Secret? = null,
|
password: Secret? = null,
|
||||||
userCanSudoWithoutPassword: Boolean = false,
|
sudo: Boolean = false,
|
||||||
copyAuthorizedSshKeysFromCurrentUser: Boolean = false
|
copyAuthorizedSshKeysFromCurrentUser: Boolean = false
|
||||||
): ProvResult = task {
|
): ProvResult = task {
|
||||||
if (!userExists(userName)) {
|
if (!userExists(userName)) {
|
||||||
cmd("sudo adduser --gecos \"First Last,RoomNumber,WorkPhone,HomePhone\" --disabled-password --home /home/$userName $userName")
|
cmd("sudo adduser --gecos \"First Last,RoomNumber,WorkPhone,HomePhone\" --disabled-password --home /home/$userName $userName")
|
||||||
}
|
}
|
||||||
password?.let { cmdNoLog("sudo echo \"$userName:${password.plain()}\" | sudo chpasswd") } ?: ProvResult(true)
|
password?.let { cmdNoLog("sudo echo \"$userName:${password.plain()}\" | sudo chpasswd") } ?: ProvResult(true)
|
||||||
if (userCanSudoWithoutPassword) {
|
if (sudo) {
|
||||||
makeUserSudoerWithoutPasswordRequired(userName)
|
makeUserSudoerWithNoSudoPasswordRequired(userName)
|
||||||
}
|
}
|
||||||
val authorizedKeysFile = userHome() + ".ssh/authorized_keys"
|
val authorizedKeysFile = userHome() + ".ssh/authorized_keys"
|
||||||
if (copyAuthorizedSshKeysFromCurrentUser && checkFile(authorizedKeysFile)) {
|
if (copyAuthorizedSshKeysFromCurrentUser && checkFile(authorizedKeysFile)) {
|
||||||
|
@ -85,11 +85,11 @@ fun Prov.deleteUser(userName: String, deleteHomeDir: Boolean = false): ProvResul
|
||||||
* The current (executing) user must already be a sudoer. If he is a sudoer with password required then
|
* The current (executing) user must already be a sudoer. If he is a sudoer with password required then
|
||||||
* his password must be provided.
|
* his password must be provided.
|
||||||
*/
|
*/
|
||||||
fun Prov.makeUserSudoerWithoutPasswordRequired(
|
fun Prov.makeUserSudoerWithNoSudoPasswordRequired(
|
||||||
userName: String,
|
userName: String,
|
||||||
password: Secret? = null,
|
password: Secret? = null,
|
||||||
overwriteFile: Boolean = false
|
overwriteFile: Boolean = false
|
||||||
): ProvResult = taskWithResult {
|
): ProvResult = task {
|
||||||
val userSudoFile = "/etc/sudoers.d/$userName"
|
val userSudoFile = "/etc/sudoers.d/$userName"
|
||||||
if (!checkFile(userSudoFile) || overwriteFile) {
|
if (!checkFile(userSudoFile) || overwriteFile) {
|
||||||
val sudoPrefix = if (password == null) "sudo" else "echo ${password.plain()} | sudo -S"
|
val sudoPrefix = if (password == null) "sudo" else "echo ${password.plain()} | sudo -S"
|
||||||
|
@ -107,10 +107,11 @@ fun Prov.makeUserSudoerWithoutPasswordRequired(
|
||||||
* Makes the current (executing) user be able to sudo without password.
|
* Makes the current (executing) user be able to sudo without password.
|
||||||
* IMPORTANT: Current user must already by sudoer when calling this function.
|
* IMPORTANT: Current user must already by sudoer when calling this function.
|
||||||
*/
|
*/
|
||||||
fun Prov.makeCurrentUserSudoerWithoutPasswordRequired(password: Secret) = taskWithResult {
|
@Suppress("unused") // used externally
|
||||||
|
fun Prov.makeUserSudoerWithNoSudoPasswordRequired(password: Secret) = task {
|
||||||
val currentUser = whoami()
|
val currentUser = whoami()
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
makeUserSudoerWithoutPasswordRequired(currentUser, password, overwriteFile = true)
|
makeUserSudoerWithNoSudoPasswordRequired(currentUser, password, overwriteFile = true)
|
||||||
} else {
|
} else {
|
||||||
ProvResult(false, "Current user could not be determined.")
|
ProvResult(false, "Current user could not be determined.")
|
||||||
}
|
}
|
||||||
|
@ -130,10 +131,11 @@ fun Prov.userIsInGroupSudo(userName: String): Boolean {
|
||||||
* Checks if current user can execute sudo commands.
|
* Checks if current user can execute sudo commands.
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun Prov.currentUserCanSudoWithoutPassword(): Boolean {
|
fun Prov.currentUserCanSudo(): Boolean {
|
||||||
return chk("timeout 1 sudo -kS id")
|
return chk("timeout 1 sudo id")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns username of current user if it can be determined
|
* Returns username of current user if it can be determined
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package org.domaindrivenarchitecture.provs.server.application
|
package org.domaindrivenarchitecture.provs.server.application
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.configuration.application.ensureSudoWithoutPassword
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.cli.createProvInstance
|
import org.domaindrivenarchitecture.provs.framework.core.cli.createProvInstance
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.cli.quit
|
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.ServerType
|
import org.domaindrivenarchitecture.provs.server.domain.ServerType
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.K3sCliCommand
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.K3sCliCommand
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.provisionK3sCommand
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.provisionK3sCommand
|
||||||
|
@ -27,16 +25,14 @@ fun main(args: Array<String>) {
|
||||||
|
|
||||||
val cmd = CliArgumentsParser("provs-server.jar subcommand target").parseCommand(checkedArgs)
|
val cmd = CliArgumentsParser("provs-server.jar subcommand target").parseCommand(checkedArgs)
|
||||||
|
|
||||||
|
// ToDo: exitProcess makes testing harder, find another solution
|
||||||
// validate parsed arguments
|
// validate parsed arguments
|
||||||
if (!cmd.isValidTarget()) {
|
if (!cmd.isValidTarget()) {
|
||||||
println("Remote or localhost not valid, please try -h for help.")
|
println("Remote or localhost not valid, please try -h for help.")
|
||||||
quit(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val prov = createProvInstance(cmd.target)
|
val prov = createProvInstance(cmd.target)
|
||||||
|
prov.provisionK3sCommand(cmd as K3sCliCommand)
|
||||||
|
|
||||||
prov.session {
|
|
||||||
ensureSudoWithoutPassword(cmd.target.remoteTarget()?.password)
|
|
||||||
provisionK3sCommand(cmd as K3sCliCommand)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package org.domaindrivenarchitecture.provs.server.application
|
package org.domaindrivenarchitecture.provs.server.application
|
||||||
|
|
||||||
import kotlinx.cli.ArgType
|
import kotlinx.cli.ArgType
|
||||||
import kotlinx.cli.ExperimentalCli
|
|
||||||
import kotlinx.cli.Subcommand
|
import kotlinx.cli.Subcommand
|
||||||
import org.domaindrivenarchitecture.provs.configuration.application.CliTargetParser
|
import org.domaindrivenarchitecture.provs.configuration.application.CliTargetParser
|
||||||
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
||||||
|
@ -12,7 +11,6 @@ import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFileName
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.K3sCliCommand
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.K3sCliCommand
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.ServerOnlyModule
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.ServerOnlyModule
|
||||||
|
|
||||||
@OptIn(ExperimentalCli::class)
|
|
||||||
class CliArgumentsParser(name: String) : CliTargetParser(name) {
|
class CliArgumentsParser(name: String) : CliTargetParser(name) {
|
||||||
|
|
||||||
private val modules: List<ServerSubcommand> = listOf(K3s(), K3d())
|
private val modules: List<ServerSubcommand> = listOf(K3s(), K3d())
|
||||||
|
@ -86,7 +84,7 @@ class CliArgumentsParser(name: String) : CliTargetParser(name) {
|
||||||
override fun execute() {
|
override fun execute() {
|
||||||
super.configFileName = cliConfigFileName?.let { ConfigFileName(it) }
|
super.configFileName = cliConfigFileName?.let { ConfigFileName(it) }
|
||||||
super.applicationFileName = cliApplicationFileName?.let { ApplicationFileName(it) }
|
super.applicationFileName = cliApplicationFileName?.let { ApplicationFileName(it) }
|
||||||
super.onlyModules = only?.let { listOf(it.name.lowercase()) }
|
super.onlyModules = if (only != null) listOf(only!!.name.lowercase()) else null
|
||||||
super.reprovision = cliReprovision == true
|
super.reprovision = cliReprovision == true
|
||||||
super.parsed = true
|
super.parsed = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
package org.domaindrivenarchitecture.provs.server.domain.k3s
|
|
||||||
|
|
||||||
data class ApplicationFile(val id: ApplicationFileName, val fileContent: String) {
|
|
||||||
|
|
||||||
fun validate() : List<String> {
|
|
||||||
val output = ArrayList<String>()
|
|
||||||
val specRegex = "Spec.failed".toRegex()
|
|
||||||
val javaRegex = "Exception.in.thread".toRegex()
|
|
||||||
|
|
||||||
if(fileContent.isEmpty()) {
|
|
||||||
output.add("fileContent is empty.")
|
|
||||||
}
|
|
||||||
val specMatch = specRegex.find(fileContent)
|
|
||||||
if (specMatch != null) {
|
|
||||||
output.add(specMatch.value)
|
|
||||||
}
|
|
||||||
val javaMatch = javaRegex.find(fileContent)
|
|
||||||
if (javaMatch != null) {
|
|
||||||
output.add(javaMatch.value)
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
fun isValid() : Boolean {
|
|
||||||
return validate().isEmpty()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,8 +2,8 @@ package org.domaindrivenarchitecture.provs.server.domain.k3s
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class ApplicationFileName(val fileName: String) {
|
data class ApplicationFileName(val fileName: String) {
|
||||||
fun fullyQualifiedName() : String {
|
fun fullqualified() : String {
|
||||||
return File(fileName).absoluteFile.absolutePath
|
return File(fileName).absoluteFile.absolutePath
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package org.domaindrivenarchitecture.provs.server.domain.k3s
|
package org.domaindrivenarchitecture.provs.server.domain.k3s
|
||||||
|
|
||||||
interface ApplicationFileRepository {
|
interface ApplicationFileRepository {
|
||||||
fun getFile() : ApplicationFile
|
fun assertExists(applicationFileName: ApplicationFileName?)
|
||||||
|
|
||||||
}
|
}
|
|
@ -14,11 +14,11 @@ fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task {
|
||||||
|
|
||||||
if (cli.onlyModules == null ) {
|
if (cli.onlyModules == null ) {
|
||||||
val k3sConfig: K3sConfig = getK3sConfig(cli.configFileName)
|
val k3sConfig: K3sConfig = getK3sConfig(cli.configFileName)
|
||||||
|
DefaultApplicationFileRepository().assertExists(cli.applicationFileName)
|
||||||
DefaultConfigFileRepository().assertExists(cli.configFileName)
|
DefaultConfigFileRepository().assertExists(cli.configFileName)
|
||||||
val k3sConfigReprovision = k3sConfig.copy(reprovision = cli.reprovision || k3sConfig.reprovision)
|
|
||||||
|
|
||||||
val applicationFile = cli.applicationFileName?.let { DefaultApplicationFileRepository(cli.applicationFileName).getFile() }
|
val k3sConfigReprovision = k3sConfig.copy(reprovision = cli.reprovision || k3sConfig.reprovision)
|
||||||
provisionK3s(k3sConfigReprovision, grafanaConfigResolved, applicationFile)
|
provisionK3s(k3sConfigReprovision, grafanaConfigResolved, cli.applicationFileName)
|
||||||
} else {
|
} else {
|
||||||
provisionGrafana(cli.onlyModules, grafanaConfigResolved)
|
provisionGrafana(cli.onlyModules, grafanaConfigResolved)
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,7 @@ fun Prov.provisionK3sCommand(cli: K3sCliCommand) = task {
|
||||||
fun Prov.provisionK3s(
|
fun Prov.provisionK3s(
|
||||||
k3sConfig: K3sConfig,
|
k3sConfig: K3sConfig,
|
||||||
grafanaConfigResolved: GrafanaAgentConfigResolved? = null,
|
grafanaConfigResolved: GrafanaAgentConfigResolved? = null,
|
||||||
applicationFile: ApplicationFile? = null
|
applicationFileName: ApplicationFileName? = null) = task {
|
||||||
) = task {
|
|
||||||
|
|
||||||
if (k3sConfig.reprovision) {
|
if (k3sConfig.reprovision) {
|
||||||
deprovisionK3sInfra()
|
deprovisionK3sInfra()
|
||||||
|
@ -53,8 +52,8 @@ fun Prov.provisionK3s(
|
||||||
provisionGrafanaAgent(grafanaConfigResolved)
|
provisionGrafanaAgent(grafanaConfigResolved)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (applicationFile != null) {
|
if (applicationFileName != null) {
|
||||||
provisionK3sApplication(applicationFile)
|
provisionK3sApplication(applicationFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!k3sConfig.reprovision) {
|
if (!k3sConfig.reprovision) {
|
||||||
|
@ -64,8 +63,7 @@ fun Prov.provisionK3s(
|
||||||
|
|
||||||
private fun Prov.provisionGrafana(
|
private fun Prov.provisionGrafana(
|
||||||
onlyModules: List<String>?,
|
onlyModules: List<String>?,
|
||||||
grafanaConfigResolved: GrafanaAgentConfigResolved?
|
grafanaConfigResolved: GrafanaAgentConfigResolved?) = task {
|
||||||
) = task {
|
|
||||||
|
|
||||||
if (onlyModules != null && onlyModules.contains(ServerOnlyModule.GRAFANA.name.lowercase())) {
|
if (onlyModules != null && onlyModules.contains(ServerOnlyModule.GRAFANA.name.lowercase())) {
|
||||||
if (grafanaConfigResolved == null) {
|
if (grafanaConfigResolved == null) {
|
||||||
|
|
|
@ -1,30 +1,14 @@
|
||||||
package org.domaindrivenarchitecture.provs.server.infrastructure
|
package org.domaindrivenarchitecture.provs.server.infrastructure
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.getLocalFileContent
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.checkLocalFile
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.checkLocalFile
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFile
|
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFileName
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFileName
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFileRepository
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFileRepository
|
||||||
|
|
||||||
|
class DefaultApplicationFileRepository : ApplicationFileRepository {
|
||||||
|
|
||||||
class DefaultApplicationFileRepository(val applicationFileName: ApplicationFileName) : ApplicationFileRepository {
|
override fun assertExists(applicationFileName: ApplicationFileName?) {
|
||||||
|
if (applicationFileName != null && !checkLocalFile(applicationFileName.fullqualified())) {
|
||||||
private fun assertExists(applicationFileName: String) {
|
throw RuntimeException("Application file ${applicationFileName.fileName} not found. Please check if path is correct.")
|
||||||
if (!checkLocalFile(applicationFileName)) {
|
|
||||||
throw RuntimeException("Application file not found. Please check if path is correct.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getFile(): ApplicationFile {
|
|
||||||
assertExists(applicationFileName.fullyQualifiedName())
|
|
||||||
|
|
||||||
val applicationFileContents = getLocalFileContent(applicationFileName.fullyQualifiedName())
|
|
||||||
val applicationFile = ApplicationFile(applicationFileName, applicationFileContents)
|
|
||||||
|
|
||||||
return if (applicationFile.isValid()) {
|
|
||||||
applicationFile
|
|
||||||
} else {
|
|
||||||
throw RuntimeException("Application file was invalid.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,10 @@ import org.domaindrivenarchitecture.provs.framework.core.ProvResult
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.repeatTaskUntilSuccess
|
import org.domaindrivenarchitecture.provs.framework.core.repeatTaskUntilSuccess
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.*
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.*
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.CertmanagerEndpoint
|
import org.domaindrivenarchitecture.provs.server.domain.CertmanagerEndpoint
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.*
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFileName
|
||||||
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.Certmanager
|
||||||
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.FileMode
|
||||||
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.K3sConfig
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
// ----------------------------------- versions --------------------------------
|
// ----------------------------------- versions --------------------------------
|
||||||
|
@ -75,35 +78,25 @@ fun Prov.installK3s(k3sConfig: K3sConfig): ProvResult {
|
||||||
if (k3sConfig.isDualStack()) {
|
if (k3sConfig.isDualStack()) {
|
||||||
k3sConfigResourceFileName += ".dual.template.yaml"
|
k3sConfigResourceFileName += ".dual.template.yaml"
|
||||||
metallbConfigResourceFileName += ".dual.template.yaml"
|
metallbConfigResourceFileName += ".dual.template.yaml"
|
||||||
require(k3sConfig.node.ipv6 != null && k3sConfig.loopback.ipv6 != null)
|
k3sConfigMap = k3sConfigMap.plus("node_ipv6" to k3sConfig.node.ipv6!!)
|
||||||
k3sConfigMap = k3sConfigMap
|
.plus("loopback_ipv6" to k3sConfig.loopback.ipv6!!)
|
||||||
.plus("node_ipv6" to k3sConfig.node.ipv6)
|
|
||||||
.plus("loopback_ipv6" to k3sConfig.loopback.ipv6)
|
|
||||||
} else {
|
} else {
|
||||||
k3sConfigResourceFileName += ".ipv4.template.yaml"
|
k3sConfigResourceFileName += ".ipv4.template.yaml"
|
||||||
metallbConfigResourceFileName += ".ipv4.template.yaml"
|
metallbConfigResourceFileName += ".ipv4.template.yaml"
|
||||||
}
|
}
|
||||||
|
|
||||||
createK3sFileFromResourceTemplate(
|
createK3sFileFromResourceTemplate(k3sConfigFile, k3sConfigMap, alternativeResourceTemplate = File(k3sConfigResourceFileName))
|
||||||
k3sConfigFile,
|
|
||||||
k3sConfigMap,
|
|
||||||
alternativeResourceTemplate = File(k3sConfigResourceFileName)
|
|
||||||
)
|
|
||||||
createK3sFileFromResource(k3sInstallScript, posixFilePermission = "755")
|
createK3sFileFromResource(k3sInstallScript, posixFilePermission = "755")
|
||||||
cmd("INSTALL_K3S_VERSION=$K3S_VERSION k3s-install.sh")
|
cmd("INSTALL_K3S_VERSION=$K3S_VERSION k3s-install.sh")
|
||||||
|
|
||||||
// metallb
|
// metallb
|
||||||
applyK3sFileFromResource(File(k3sManualManifestsDir, "metallb-0.13.7-native-manifest.yaml"))
|
applyK3sFileFromResource(File(k3sManualManifestsDir, "metallb-namespace.yaml"))
|
||||||
|
applyK3sFileFromResource(File(k3sManualManifestsDir, "metallb-0.10.2-manifest.yaml"))
|
||||||
repeatTaskUntilSuccess(6, 10) {
|
|
||||||
applyK3sFileFromResourceTemplate(
|
applyK3sFileFromResourceTemplate(
|
||||||
File(k3sManualManifestsDir, "metallb-config.yaml"),
|
File(k3sManualManifestsDir, "metallb-config.yaml"),
|
||||||
k3sConfigMap,
|
k3sConfigMap,
|
||||||
alternativeResourceName = File(metallbConfigResourceFileName)
|
alternativeResourceName = File(metallbConfigResourceFileName)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
applyK3sFileFromResource(File(k3sManualManifestsDir, "metallb-l2advertisement.yaml"))
|
|
||||||
|
|
||||||
// traefik
|
// traefik
|
||||||
if (k3sConfig.isDualStack()) {
|
if (k3sConfig.isDualStack()) {
|
||||||
|
@ -157,9 +150,9 @@ fun Prov.provisionK3sEcho(fqdn: String, endpoint: CertmanagerEndpoint? = null) =
|
||||||
applyK3sFileFromResourceTemplate(k3sEcho, mapOf("fqdn" to fqdn, "issuer_name" to issuer))
|
applyK3sFileFromResourceTemplate(k3sEcho, mapOf("fqdn" to fqdn, "issuer_name" to issuer))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.provisionK3sApplication(applicationFile: ApplicationFile) = task {
|
fun Prov.provisionK3sApplication(applicationFileName: ApplicationFileName) = task {
|
||||||
copyFileFromLocal(
|
copyFileFromLocal(
|
||||||
fullyQualifiedLocalFilename = applicationFile.id.fullyQualifiedName(),
|
fullyQualifiedLocalFilename = applicationFileName.fullqualified(),
|
||||||
fullyQualifiedFilename = k3sManualManifestsDir + "application.yaml",
|
fullyQualifiedFilename = k3sManualManifestsDir + "application.yaml",
|
||||||
posixFilePermission = "644",
|
posixFilePermission = "644",
|
||||||
sudo = true
|
sudo = true
|
||||||
|
@ -223,5 +216,5 @@ private fun File.templateName(): String {
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Prov.configureShellAliases() = task {
|
internal fun Prov.configureShellAliases() = task {
|
||||||
addTextToFile("\nalias k=\"sudo kubectl\"\n", File(".bash_aliases"))
|
addTextToFile( "\nalias k=\"sudo kubectl\"\n", File(".bash_aliases",))
|
||||||
}
|
}
|
|
@ -16,12 +16,11 @@ fun Prov.testNetworkExists(): Boolean {
|
||||||
fun Prov.provisionNetwork(k3sConfig: K3sConfig) = task {
|
fun Prov.provisionNetwork(k3sConfig: K3sConfig) = task {
|
||||||
if(!testNetworkExists()) {
|
if(!testNetworkExists()) {
|
||||||
if(k3sConfig.isDualStack()) {
|
if(k3sConfig.isDualStack()) {
|
||||||
require(k3sConfig.loopback.ipv6 != null)
|
|
||||||
createFileFromResourceTemplate(
|
createFileFromResourceTemplate(
|
||||||
loopbackFile,
|
loopbackFile,
|
||||||
"99-loopback.dual.template.yaml",
|
"99-loopback.dual.template.yaml",
|
||||||
resourcePathNetwork,
|
resourcePathNetwork,
|
||||||
mapOf("loopback_ipv4" to k3sConfig.loopback.ipv4, "loopback_ipv6" to k3sConfig.loopback.ipv6),
|
mapOf("loopback_ipv4" to k3sConfig.loopback.ipv4, "loopback_ipv6" to k3sConfig.loopback.ipv6!!),
|
||||||
"644",
|
"644",
|
||||||
sudo = true
|
sudo = true
|
||||||
)
|
)
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,19 +1,18 @@
|
||||||
apiVersion: metallb.io/v1beta1
|
apiVersion: v1
|
||||||
kind: IPAddressPool
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: public
|
|
||||||
namespace: metallb-system
|
namespace: metallb-system
|
||||||
spec:
|
name: config
|
||||||
|
data:
|
||||||
|
config: |
|
||||||
|
address-pools:
|
||||||
|
- name: public
|
||||||
|
protocol: layer2
|
||||||
addresses:
|
addresses:
|
||||||
- ${node_ipv4}/32
|
- ${node_ipv4}/32
|
||||||
- ${node_ipv6}/128
|
- ${node_ipv6}/128
|
||||||
---
|
- name: private
|
||||||
apiVersion: metallb.io/v1beta1
|
protocol: layer2
|
||||||
kind: IPAddressPool
|
|
||||||
metadata:
|
|
||||||
name: private
|
|
||||||
namespace: metallb-system
|
|
||||||
spec:
|
|
||||||
addresses:
|
addresses:
|
||||||
- ${loopback_ipv4}/32
|
- ${loopback_ipv4}/32
|
||||||
- ${loopback_ipv6}/128
|
- ${loopback_ipv6}/128
|
|
@ -1,17 +1,16 @@
|
||||||
apiVersion: metallb.io/v1beta1
|
apiVersion: v1
|
||||||
kind: IPAddressPool
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: public
|
|
||||||
namespace: metallb-system
|
namespace: metallb-system
|
||||||
spec:
|
name: config
|
||||||
|
data:
|
||||||
|
config: |
|
||||||
|
address-pools:
|
||||||
|
- name: public
|
||||||
|
protocol: layer2
|
||||||
addresses:
|
addresses:
|
||||||
- ${node_ipv4}/32
|
- ${node_ipv4}/32
|
||||||
---
|
- name: private
|
||||||
apiVersion: metallb.io/v1beta1
|
protocol: layer2
|
||||||
kind: IPAddressPool
|
|
||||||
metadata:
|
|
||||||
name: private
|
|
||||||
namespace: metallb-system
|
|
||||||
spec:
|
|
||||||
addresses:
|
addresses:
|
||||||
- ${loopback_ipv4}/32
|
- ${loopback_ipv4}/32
|
|
@ -1,9 +0,0 @@
|
||||||
apiVersion: metallb.io/v1beta1
|
|
||||||
kind: L2Advertisement
|
|
||||||
metadata:
|
|
||||||
name: layer2
|
|
||||||
namespace: metallb-system
|
|
||||||
spec:
|
|
||||||
ipAddressPools:
|
|
||||||
- private
|
|
||||||
- public
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: metallb-system
|
||||||
|
labels:
|
||||||
|
app: metallb
|
|
@ -11,4 +11,4 @@ command:
|
||||||
- command: "python3 --version"
|
- command: "python3 --version"
|
||||||
- command: "pip3 --version"
|
- command: "pip3 --version"
|
||||||
- command: "terraform --version"
|
- command: "terraform --version"
|
||||||
out: "1.4.6"
|
out: "1.0.8"
|
||||||
|
|
|
@ -1,21 +1,9 @@
|
||||||
package org.domaindrivenarchitecture.provs.configuration.application
|
package org.domaindrivenarchitecture.provs.configuration.application
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
|
|
||||||
import org.junit.jupiter.api.Assertions.*
|
import org.junit.jupiter.api.Assertions.*
|
||||||
import org.junit.jupiter.api.Disabled
|
import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
|
||||||
private fun parseTarget(
|
|
||||||
args: Array<String>
|
|
||||||
): TargetCliCommand {
|
|
||||||
val parser = CliTargetParser("provs")
|
|
||||||
|
|
||||||
parser.parse(args)
|
|
||||||
|
|
||||||
return TargetCliCommand(parser.target, parser.passwordInteractive)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class CliTargetParserTest {
|
internal class CliTargetParserTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
package org.domaindrivenarchitecture.provs.configuration.application
|
|
||||||
|
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.mockkStatic
|
|
||||||
import io.mockk.unmockkStatic
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.*
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.cli.getPasswordToConfigureSudoWithoutPassword
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.docker.provideContainer
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.processors.ContainerStartMode
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.processors.ContainerUbuntuHostProcessor
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.deleteFile
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.currentUserCanSudoWithoutPassword
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.makeCurrentUserSudoerWithoutPasswordRequired
|
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
|
||||||
|
|
||||||
|
|
||||||
class ProvWithSudoKtTest {
|
|
||||||
|
|
||||||
|
|
||||||
@ExtensiveContainerTest
|
|
||||||
fun test_ensureSudoWithoutPassword_local_Prov() {
|
|
||||||
|
|
||||||
mockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
|
||||||
every { getPasswordToConfigureSudoWithoutPassword() } returns Secret("testuserpw")
|
|
||||||
|
|
||||||
// given
|
|
||||||
val containerName = "prov-test-sudo-no-pw"
|
|
||||||
local().provideContainer(containerName, "ubuntu_plus_user")
|
|
||||||
val prov = Prov.newInstance(
|
|
||||||
ContainerUbuntuHostProcessor(
|
|
||||||
containerName,
|
|
||||||
startMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
|
||||||
sudo = true,
|
|
||||||
dockerImage = "ubuntu_plus_user"
|
|
||||||
),
|
|
||||||
progressType = ProgressType.NONE
|
|
||||||
)
|
|
||||||
prov.deleteFile("/etc/sudoers.d/testuser", sudo = true) // remove no password required config
|
|
||||||
|
|
||||||
// when
|
|
||||||
val canSudo1 = prov.currentUserCanSudoWithoutPassword()
|
|
||||||
prov.ensureSudoWithoutPassword(null)
|
|
||||||
val canSudo2 = prov.currentUserCanSudoWithoutPassword()
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertFalse(canSudo1)
|
|
||||||
assertTrue(canSudo2)
|
|
||||||
|
|
||||||
unmockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExtensiveContainerTest
|
|
||||||
fun test_ensureSudoWithoutPassword_remote_Prov() {
|
|
||||||
|
|
||||||
// given
|
|
||||||
val containerName = "prov-test-sudo-no-pw-ssh"
|
|
||||||
val password = Secret("testuserpw")
|
|
||||||
|
|
||||||
val prov = Prov.newInstance(
|
|
||||||
ContainerUbuntuHostProcessor(
|
|
||||||
containerName,
|
|
||||||
startMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
|
||||||
sudo = true,
|
|
||||||
dockerImage = "ubuntu_plus_user",
|
|
||||||
options = "--expose=22"
|
|
||||||
),
|
|
||||||
progressType = ProgressType.NONE
|
|
||||||
)
|
|
||||||
prov.makeCurrentUserSudoerWithoutPasswordRequired(password)
|
|
||||||
prov.task {
|
|
||||||
aptInstall("openssh-server")
|
|
||||||
cmd("sudo service ssh start")
|
|
||||||
deleteFile("/etc/sudoers.d/testuser", sudo = true) // remove no password required config
|
|
||||||
}
|
|
||||||
val ip = local().cmd("sudo docker inspect -f \"{{ .NetworkSettings.IPAddress }}\" $containerName").out?.trim()
|
|
||||||
?: throw IllegalStateException("Ip not found")
|
|
||||||
val remoteProvBySsh = remote(ip, "testuser", password)
|
|
||||||
|
|
||||||
// when
|
|
||||||
val canSudo1 = remoteProvBySsh.currentUserCanSudoWithoutPassword()
|
|
||||||
prov.ensureSudoWithoutPassword(password)
|
|
||||||
val canSudo2 = prov.currentUserCanSudoWithoutPassword()
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertFalse(canSudo1)
|
|
||||||
assertTrue(canSudo2)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,11 +4,10 @@ import io.mockk.*
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.cli.createProvInstance
|
import org.domaindrivenarchitecture.provs.framework.core.cli.createProvInstance
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.cli.getPasswordToConfigureSudoWithoutPassword
|
import org.domaindrivenarchitecture.provs.framework.core.cli.retrievePassword
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.local
|
import org.domaindrivenarchitecture.provs.framework.core.local
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.processors.PrintOnlyProcessor
|
import org.domaindrivenarchitecture.provs.framework.core.processors.PrintOnlyProcessor
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.remote
|
import org.domaindrivenarchitecture.provs.framework.core.remote
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.NonCi
|
|
||||||
import org.junit.jupiter.api.AfterAll
|
import org.junit.jupiter.api.AfterAll
|
||||||
import org.junit.jupiter.api.BeforeAll
|
import org.junit.jupiter.api.BeforeAll
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
@ -24,8 +23,8 @@ internal class CliTargetCommandKtTest {
|
||||||
mockkStatic(::remote)
|
mockkStatic(::remote)
|
||||||
every { remote(any(), any(), any(), any()) } returns Prov.newInstance(PrintOnlyProcessor())
|
every { remote(any(), any(), any(), any()) } returns Prov.newInstance(PrintOnlyProcessor())
|
||||||
|
|
||||||
mockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
mockkStatic(::retrievePassword)
|
||||||
every { getPasswordToConfigureSudoWithoutPassword() } returns Secret("sec")
|
every { retrievePassword(any()) } returns Secret("sec")
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterAll
|
@AfterAll
|
||||||
|
@ -34,13 +33,12 @@ internal class CliTargetCommandKtTest {
|
||||||
unmockkObject(Prov)
|
unmockkObject(Prov)
|
||||||
unmockkStatic(::local)
|
unmockkStatic(::local)
|
||||||
unmockkStatic(::remote)
|
unmockkStatic(::remote)
|
||||||
unmockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
unmockkStatic(::retrievePassword)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@NonCi
|
|
||||||
fun createProvInstance_local() {
|
fun createProvInstance_local() {
|
||||||
// given
|
// given
|
||||||
val cliCommand = TargetCliCommand("local", false)
|
val cliCommand = TargetCliCommand("local", false)
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package org.domaindrivenarchitecture.provs.configuration.infrastructure
|
package org.domaindrivenarchitecture.provs.configuration.infrastructure
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFileName
|
||||||
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileRepository
|
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFile
|
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFileRepository
|
import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFileRepository
|
||||||
import org.domaindrivenarchitecture.provs.server.infrastructure.DefaultApplicationFileRepository
|
import org.domaindrivenarchitecture.provs.server.infrastructure.DefaultApplicationFileRepository
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
@ -14,8 +12,8 @@ internal class DefaultConfigFileRepositoryKtTest {
|
||||||
@Test
|
@Test
|
||||||
fun assertExistsThrowsRuntimeException() {
|
fun assertExistsThrowsRuntimeException() {
|
||||||
// when
|
// when
|
||||||
val invalidFileName = ConfigFileName("iDontExist")
|
val invalidFileName = ApplicationFileName("iDontExist")
|
||||||
val repo: ConfigFileRepository = DefaultConfigFileRepository()
|
val repo: ApplicationFileRepository = DefaultApplicationFileRepository()
|
||||||
|
|
||||||
// then
|
// then
|
||||||
val exception = assertThrows<RuntimeException>(
|
val exception = assertThrows<RuntimeException>(
|
||||||
|
@ -23,18 +21,18 @@ internal class DefaultConfigFileRepositoryKtTest {
|
||||||
) { repo.assertExists(invalidFileName) }
|
) { repo.assertExists(invalidFileName) }
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"Config file not found. Please check if path is correct.",
|
"Application file iDontExist not found. Please check if path is correct.",
|
||||||
exception.message)
|
exception.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun assertExistsPasses() {
|
fun assertExistsPasses() {
|
||||||
// given
|
// given
|
||||||
val validFileName = "src/test/resources/existing-file"
|
val validFileName = "src/test/resources/existing_file"
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val validFile = ConfigFileName(File(validFileName).path)
|
val validFile = ApplicationFileName(File(validFileName).path)
|
||||||
val repo: ConfigFileRepository = DefaultConfigFileRepository()
|
val repo: ApplicationFileRepository = DefaultApplicationFileRepository()
|
||||||
repo.assertExists(validFile)
|
repo.assertExists(validFile)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
|
|
|
@ -4,21 +4,16 @@ import ch.qos.logback.classic.Level
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
||||||
import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
|
import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
|
||||||
import org.domaindrivenarchitecture.provs.desktop.domain.DesktopCliCommand
|
import org.domaindrivenarchitecture.provs.desktop.domain.*
|
||||||
import org.domaindrivenarchitecture.provs.desktop.domain.DesktopConfig
|
|
||||||
import org.domaindrivenarchitecture.provs.desktop.domain.DesktopType
|
|
||||||
import org.domaindrivenarchitecture.provs.desktop.domain.provisionDesktop
|
|
||||||
import org.domaindrivenarchitecture.provs.desktop.infrastructure.getConfig
|
import org.domaindrivenarchitecture.provs.desktop.infrastructure.getConfig
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.*
|
import org.domaindrivenarchitecture.provs.framework.core.*
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.cli.getPasswordToConfigureSudoWithoutPassword
|
import org.domaindrivenarchitecture.provs.framework.core.cli.retrievePassword
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.cli.quit
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.processors.DummyProcessor
|
import org.domaindrivenarchitecture.provs.framework.core.processors.DummyProcessor
|
||||||
import org.domaindrivenarchitecture.provs.test.setRootLoggingLevel
|
import org.domaindrivenarchitecture.provs.test.setRootLoggingLevel
|
||||||
import org.junit.jupiter.api.AfterAll
|
import org.junit.jupiter.api.AfterAll
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.BeforeAll
|
import org.junit.jupiter.api.BeforeAll
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.assertThrows
|
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
|
|
||||||
|
@ -57,8 +52,8 @@ internal class ApplicationKtTest {
|
||||||
cmd = "mocked command"
|
cmd = "mocked command"
|
||||||
)
|
)
|
||||||
|
|
||||||
mockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
mockkStatic(::retrievePassword)
|
||||||
every { getPasswordToConfigureSudoWithoutPassword() } returns Secret("sec")
|
every { retrievePassword(any()) } returns Secret("sec")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused") // false positive
|
@Suppress("unused") // false positive
|
||||||
|
@ -70,7 +65,7 @@ internal class ApplicationKtTest {
|
||||||
unmockkStatic(::remote)
|
unmockkStatic(::remote)
|
||||||
unmockkStatic(::getConfig)
|
unmockkStatic(::getConfig)
|
||||||
unmockkStatic(Prov::provisionDesktop)
|
unmockkStatic(Prov::provisionDesktop)
|
||||||
unmockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
unmockkStatic(::retrievePassword)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,9 +91,6 @@ internal class ApplicationKtTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun prints_error_message_if_config_not_found() {
|
fun prints_error_message_if_config_not_found() {
|
||||||
mockkStatic(::quit)
|
|
||||||
every { quit(any()) } throws RuntimeException("mockked")
|
|
||||||
|
|
||||||
// given
|
// given
|
||||||
setRootLoggingLevel(Level.OFF)
|
setRootLoggingLevel(Level.OFF)
|
||||||
|
|
||||||
|
@ -111,28 +103,21 @@ internal class ApplicationKtTest {
|
||||||
System.setErr(PrintStream(errContent))
|
System.setErr(PrintStream(errContent))
|
||||||
|
|
||||||
// when
|
// when
|
||||||
assertThrows<RuntimeException> {
|
|
||||||
main(arrayOf("basic", "someuser@remotehost", "-c", "idontexist.yaml"))
|
main(arrayOf("basic", "someuser@remotehost", "-c", "idontexist.yaml"))
|
||||||
}
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
System.setOut(originalOut)
|
System.setOut(originalOut)
|
||||||
System.setErr(originalErr)
|
System.setErr(originalErr)
|
||||||
|
|
||||||
val expectedOutput =
|
val expectedOutput =
|
||||||
"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."
|
"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."
|
||||||
assertEquals(expectedOutput, outContent.toString().replace("\r", "").replace("\n", ""))
|
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(), any()) }
|
||||||
|
|
||||||
unmockkStatic(::quit)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun prints_error_message_if_config_not_parsable() {
|
fun prints_error_message_if_config_not_parsable() {
|
||||||
mockkStatic(::quit)
|
|
||||||
every { quit(any()) } throws RuntimeException("mockked")
|
|
||||||
|
|
||||||
// given
|
// given
|
||||||
setRootLoggingLevel(Level.OFF)
|
setRootLoggingLevel(Level.OFF)
|
||||||
|
|
||||||
|
@ -145,20 +130,16 @@ internal class ApplicationKtTest {
|
||||||
System.setErr(PrintStream(errContent))
|
System.setErr(PrintStream(errContent))
|
||||||
|
|
||||||
// when
|
// when
|
||||||
assertThrows<RuntimeException> {
|
|
||||||
main(arrayOf("basic", "someuser@remotehost", "-c", "src/test/resources/invalid-desktop-config.yaml"))
|
main(arrayOf("basic", "someuser@remotehost", "-c", "src/test/resources/invalid-desktop-config.yaml"))
|
||||||
}
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
System.setOut(originalOut)
|
System.setOut(originalOut)
|
||||||
System.setErr(originalErr)
|
System.setErr(originalErr)
|
||||||
|
|
||||||
val expectedOutput =
|
val expectedOutput =
|
||||||
"Error: File \"src/test/resources/invalid-desktop-config.yaml\" has an invalid format and or invalid data.No suitable config found."
|
"Error: File \"src/test/resources/invalid-desktop-config.yaml\" has an invalid format and or invalid data."
|
||||||
assertEquals(expectedOutput, outContent.toString().replace("\r", "").replace("\n", ""))
|
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(), any()) }
|
||||||
|
|
||||||
unmockkStatic(::quit)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,4 +14,13 @@ internal class CliArgumentsParserTest {
|
||||||
assertEquals(null, cli.configFile)
|
assertEquals(null, cli.configFile)
|
||||||
assertEquals(true, cli.target.isValidLocalhost())
|
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,16 +1,9 @@
|
||||||
package org.domaindrivenarchitecture.provs.desktop.domain
|
package org.domaindrivenarchitecture.provs.desktop.domain
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.ProgressType
|
import org.domaindrivenarchitecture.provs.desktop.infrastructure.getConfig
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
|
||||||
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.remote
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.deleteFile
|
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||||
import org.junit.jupiter.api.Assertions
|
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Disabled
|
import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
@ -18,34 +11,6 @@ import org.junit.jupiter.api.Test
|
||||||
internal class DesktopServiceKtTest {
|
internal class DesktopServiceKtTest {
|
||||||
|
|
||||||
@ExtensiveContainerTest
|
@ExtensiveContainerTest
|
||||||
fun provisionLocalDesktop_fails_if_user_cannot_sudo_without_password() {
|
|
||||||
// given
|
|
||||||
val containerName = "prov-test-sudo-no-pw"
|
|
||||||
local().provideContainer(containerName, "ubuntu_plus_user")
|
|
||||||
val prov = Prov.newInstance(
|
|
||||||
ContainerUbuntuHostProcessor(
|
|
||||||
containerName,
|
|
||||||
startMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
|
||||||
sudo = true,
|
|
||||||
dockerImage = "ubuntu_plus_user"
|
|
||||||
),
|
|
||||||
progressType = ProgressType.NONE
|
|
||||||
)
|
|
||||||
prov.deleteFile("/etc/sudoers.d/testuser", sudo = true) // remove no password required
|
|
||||||
|
|
||||||
// when
|
|
||||||
Assertions.assertThrows(Exception::class.java) {
|
|
||||||
prov.provisionDesktop(
|
|
||||||
DesktopType.BASIC,
|
|
||||||
gitUserName = "testuser",
|
|
||||||
gitEmail = "testuser@test.org",
|
|
||||||
onlyModules = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExtensiveContainerTest
|
|
||||||
@Disabled("Takes very long, enable if you want to test a desktop setup")
|
|
||||||
fun provisionDesktop() {
|
fun provisionDesktop() {
|
||||||
// given
|
// given
|
||||||
val prov = defaultTestContainer()
|
val prov = defaultTestContainer()
|
||||||
|
@ -87,6 +52,28 @@ internal class DesktopServiceKtTest {
|
||||||
// then
|
// then
|
||||||
assertTrue(res.success)
|
assertTrue(res.success)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ExtensiveContainerTest
|
||||||
|
fun provisionDesktopFromConfigFile() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
// in order to test DesktopType.OFFICE: fix installing libreoffice for a fresh container as it hangs the first time but succeeds 2nd time
|
||||||
|
val config = getConfig("src/test/resources/desktop-config-example.json")
|
||||||
|
val res = prov.provisionDesktop(
|
||||||
|
DesktopType.BASIC,
|
||||||
|
config.ssh?.keyPair(),
|
||||||
|
config.gpg?.keyPair(),
|
||||||
|
config.gitUserName,
|
||||||
|
config.gitEmail,
|
||||||
|
onlyModules = null
|
||||||
|
)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.fileC
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Disabled
|
|
||||||
|
|
||||||
internal class DevOpsKtTest {
|
internal class DevOpsKtTest {
|
||||||
|
|
||||||
|
@ -35,17 +34,4 @@ internal class DevOpsKtTest {
|
||||||
defaultTestContainer().checkFile("/etc/bash_completion.d/kubernetes", sudo = true)
|
defaultTestContainer().checkFile("/etc/bash_completion.d/kubernetes", sudo = true)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExtensiveContainerTest
|
|
||||||
@Disabled("Part of test installKubectlAndTools, but can be tested separately by this test if required")
|
|
||||||
fun installKubectl() {
|
|
||||||
// given
|
|
||||||
val prov = defaultTestContainer()
|
|
||||||
|
|
||||||
// when
|
|
||||||
val res = prov.installKubectl()
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertTrue(res.success)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +1,23 @@
|
||||||
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.remote
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.isPackageInstalled
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.checkPackageInstalled
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.PromptSecretSource
|
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Disabled
|
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
internal class FirefoxKtTest {
|
internal class FirefoxKtTest {
|
||||||
|
|
||||||
// Attention: this test does not test full functionality of installFirefox, e.g. does not test
|
@Test
|
||||||
// remove snap, as this test runs against a container which does not have snap-firefox installed
|
|
||||||
@ExtensiveContainerTest
|
@ExtensiveContainerTest
|
||||||
fun installFirefox() {
|
fun installFirefox() {
|
||||||
// when
|
// when
|
||||||
val result = defaultTestContainer().session {
|
val res = defaultTestContainer().installFirefox()
|
||||||
installFirefox()
|
|
||||||
checkPackageInstalled("firefox")
|
|
||||||
}
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(result.success)
|
assertTrue(res.success)
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
val ffIsInstalled = defaultTestContainer().isPackageInstalled("firefox")
|
||||||
@Disabled("Update connection details,then enable and run manually")
|
assertTrue(ffIsInstalled)
|
||||||
fun installFirefox_remotely() {
|
|
||||||
val host = "192.168.56.123"
|
|
||||||
val user = "user"
|
|
||||||
var firefoxVersion = ""
|
|
||||||
|
|
||||||
// when
|
|
||||||
val result = remote(
|
|
||||||
host,
|
|
||||||
user,
|
|
||||||
/* remove for ssh authentication */
|
|
||||||
PromptSecretSource("Remote password for user $user").secret()
|
|
||||||
).session {
|
|
||||||
installFirefox()
|
|
||||||
firefoxVersion = cmd("apt list firefox --installed").out ?: ""
|
|
||||||
checkPackageInstalled("firefox")
|
|
||||||
}
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertTrue(result.success)
|
|
||||||
println("Firefox: $firefoxVersion")
|
|
||||||
assertTrue(firefoxVersion.contains("build") && !firefoxVersion.contains("snap"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.ProvResult
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.docker.exitAndRmContainer
|
import org.domaindrivenarchitecture.provs.framework.core.docker.exitAndRmContainer
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.local
|
import org.domaindrivenarchitecture.provs.framework.core.local
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.processors.ContainerStartMode
|
import org.domaindrivenarchitecture.provs.framework.core.processors.ContainerStartMode
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.KeyPair
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.KeyPair
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.configureGpgKeys
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.configureGpgKeys
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.NonCi
|
import org.domaindrivenarchitecture.provs.test.tags.NonCi
|
||||||
import org.domaindrivenarchitecture.provs.test_keys.privateGPGSnakeoilKey
|
import org.domaindrivenarchitecture.provs.test_keys.privateGPGSnakeoilKey
|
||||||
|
@ -23,10 +27,11 @@ internal class GopassBridgeKtTest {
|
||||||
fun test_downloadGopassBridge() {
|
fun test_downloadGopassBridge() {
|
||||||
// given
|
// given
|
||||||
local().exitAndRmContainer("provs_test")
|
local().exitAndRmContainer("provs_test")
|
||||||
val prov = defaultTestContainer()
|
val a = defaultTestContainer()
|
||||||
|
a.aptInstallCurl()
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val res = prov.downloadGopassBridge()
|
val res = a.downloadGopassBridge()
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(res.success)
|
assertTrue(res.success)
|
||||||
|
@ -37,81 +42,105 @@ internal class GopassBridgeKtTest {
|
||||||
fun test_install_and_configure_GopassBridgeJsonApi() {
|
fun test_install_and_configure_GopassBridgeJsonApi() {
|
||||||
// given
|
// given
|
||||||
local().exitAndRmContainer("provs_test")
|
local().exitAndRmContainer("provs_test")
|
||||||
val prov = defaultTestContainer()
|
val a = defaultTestContainer()
|
||||||
val preparationResult = prov.task {
|
val preparationResult = a.task {
|
||||||
|
aptInstallCurl()
|
||||||
configureGpgKeys(
|
configureGpgKeys(
|
||||||
KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())),
|
KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())),
|
||||||
trust = true,
|
trust = true,
|
||||||
skipIfExistin = false
|
skipIfExistin = false
|
||||||
)
|
)
|
||||||
installGopass()
|
installGopass()
|
||||||
configureGopass(publicGpgKey = Secret(publicGPGSnakeoilKey()))
|
if (!chk("gopass ls")) {
|
||||||
|
// configure/init gopass in default location with gpg-key-fingerprint of snakeoil keys
|
||||||
|
cmd("printf \"\\ntest\\ntest@test.org\\n\" | gopass init 0x0674104CA81A4905")
|
||||||
|
} else {
|
||||||
|
ProvResult(true, out = "gopass already configured")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assertTrue(preparationResult.success)
|
assertTrue(preparationResult.success)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val res = prov.task {
|
val res = a.task {
|
||||||
installGopassJsonApi()
|
installGopassBridgeJsonApi()
|
||||||
configureGopassJsonApi()
|
configureGopassBridgeJsonApi()
|
||||||
}
|
}
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(res.success)
|
assertTrue(res.success)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExtensiveContainerTest
|
@ContainerTest
|
||||||
@Test
|
@Test
|
||||||
@NonCi
|
@NonCi
|
||||||
@Disabled // long running test (> 1 min); if needed enable test and run manually
|
@Disabled // long running test (> 1 min); if needed enable test and run manually
|
||||||
fun test_install_GopassBridgeJsonApi_with_incompatible_gopass_jsonapi_version_installed() {
|
fun test_install_GopassBridgeJsonApi_with_incompatible_gopass_jsonapi_version_installed() {
|
||||||
// given
|
// given
|
||||||
val prov = defaultTestContainer(ContainerStartMode.CREATE_NEW_KILL_EXISTING)
|
val a = defaultTestContainer(ContainerStartMode.CREATE_NEW_KILL_EXISTING)
|
||||||
val preparationResult = prov.task {
|
val preparationResult = a.task {
|
||||||
|
aptInstallCurl()
|
||||||
|
|
||||||
configureGpgKeys(
|
configureGpgKeys(
|
||||||
KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())),
|
KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())),
|
||||||
trust = true,
|
trust = true,
|
||||||
skipIfExistin = false
|
skipIfExistin = false
|
||||||
)
|
)
|
||||||
installGopass("1.11.0", enforceVersion = true, "1ec9e0dfcfd9bcc241943e1a7d92f31bf3e66bb16f61ae5d079981325c31baa6")
|
installGopass("1.11.0", enforceVersion = true, "1ec9e0dfcfd9bcc241943e1a7d92f31bf3e66bb16f61ae5d079981325c31baa6")
|
||||||
configureGopass(publicGpgKey = Secret(publicGPGSnakeoilKey()))
|
if (!chk("gopass ls")) {
|
||||||
|
// configure gopass in default location with gpg-key-fingerprint of snakeoil keys
|
||||||
|
cmd("printf \"\\ntest\\ntest@test.org\\n\" | gopass init 0x0674104CA81A4905")
|
||||||
|
} else {
|
||||||
|
ProvResult(true, out = "gopass already configured")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assertTrue(preparationResult.success)
|
assertTrue(preparationResult.success)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val res = prov.task {
|
val res = a.task {
|
||||||
installGopassJsonApi()
|
installGopassBridgeJsonApi()
|
||||||
configureGopassJsonApi()
|
configureGopassBridgeJsonApi()
|
||||||
}
|
}
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertFalse(res.success)
|
assertFalse(res.success)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExtensiveContainerTest
|
@ContainerTest
|
||||||
@Test
|
@Test
|
||||||
@NonCi
|
@NonCi
|
||||||
@Disabled // long running test (> 1 min); if needed, enable test and run manually
|
@Disabled // long running test (> 1 min); if needed enable test and run manually
|
||||||
fun test_install_GopassBridgeJsonApi_with_incompatible_gopass_version_installed() {
|
fun test_install_GopassBridgeJsonApi_with_incompatible_gopass_version_installed() {
|
||||||
// given
|
// given
|
||||||
val prov = defaultTestContainer(ContainerStartMode.CREATE_NEW_KILL_EXISTING)
|
val a = defaultTestContainer(ContainerStartMode.CREATE_NEW_KILL_EXISTING)
|
||||||
val preparationResult = prov.task {
|
val preparationResult = a.task {
|
||||||
|
aptInstallCurl()
|
||||||
configureGpgKeys(
|
configureGpgKeys(
|
||||||
KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())),
|
KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())),
|
||||||
trust = true,
|
trust = true,
|
||||||
skipIfExistin = false
|
skipIfExistin = false
|
||||||
)
|
)
|
||||||
installGopass("1.9.0", enforceVersion = true, "fe13ef810d7fe200495107161e99eac081368aa0ce5e53971b1bd47a64eba4db")
|
installGopass("1.9.0", enforceVersion = true, "fe13ef810d7fe200495107161e99eac081368aa0ce5e53971b1bd47a64eba4db")
|
||||||
configureGopass(publicGpgKey = Secret(publicGPGSnakeoilKey()))
|
if (!chk("gopass ls")) {
|
||||||
|
// configure gopass in default location with gpg-key-fingerprint of snakeoil keys
|
||||||
|
cmd("printf \"\\ntest\\ntest@test.org\\n\" | gopass init 0x0674104CA81A4905")
|
||||||
|
} else {
|
||||||
|
ProvResult(true, out = "gopass already configured")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assertTrue(preparationResult.success)
|
assertTrue(preparationResult.success)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val res = prov.task {
|
val res = a.task {
|
||||||
installGopassJsonApi()
|
installGopassBridgeJsonApi()
|
||||||
configureGopassJsonApi()
|
configureGopassBridgeJsonApi()
|
||||||
}
|
}
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertFalse(res.success)
|
assertFalse(res.success)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Prov.aptInstallCurl() = task {
|
||||||
|
cmd("apt-get update", sudo = true)
|
||||||
|
aptInstall("curl")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.Secret
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.remote
|
import org.domaindrivenarchitecture.provs.framework.core.remote
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.KeyPair
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.KeyPair
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.configureGpgKeys
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.configureGpgKeys
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.gpgFingerprint
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.base.gpgFingerprint
|
||||||
|
@ -10,6 +12,8 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Disabled
|
import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.domaindrivenarchitecture.provs.test_keys.privateGPGSnakeoilKey
|
||||||
|
import org.domaindrivenarchitecture.provs.test_keys.publicGPGSnakeoilKey
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.*
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.*
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
@ -21,7 +25,7 @@ internal class GopassKtTest {
|
||||||
fun test_configureGopass_fails_with_path_starting_with_tilde() {
|
fun test_configureGopass_fails_with_path_starting_with_tilde() {
|
||||||
// when
|
// when
|
||||||
val res = defaultTestContainer().task {
|
val res = defaultTestContainer().task {
|
||||||
deleteFile(".config/gopass/config")
|
deleteFile(".config/gopass/config.yml")
|
||||||
configureGopass("~/somedir")
|
configureGopass("~/somedir")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,27 +36,32 @@ internal class GopassKtTest {
|
||||||
@ExtensiveContainerTest
|
@ExtensiveContainerTest
|
||||||
fun test_installAndConfigureGopassAndMountStore() {
|
fun test_installAndConfigureGopassAndMountStore() {
|
||||||
// given
|
// given
|
||||||
val prov = defaultTestContainer()
|
val a = defaultTestContainer()
|
||||||
val gopassRootDir = ".password-store"
|
val gopassRootDir = ".password-store"
|
||||||
|
a.aptInstall("wget git gnupg")
|
||||||
|
a.createDir(gopassRootDir, "~/")
|
||||||
|
a.cmd("git init", "~/$gopassRootDir")
|
||||||
|
val fpr = a.gpgFingerprint(publicGPGSnakeoilKey())
|
||||||
|
println("+++++++++++++++++++++++++++++++++++++ $fpr +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
|
||||||
|
a.createFile("~/" + gopassRootDir + "/.gpg-id", fpr)
|
||||||
|
|
||||||
|
a.createDir("exampleStoreFolder", "~/")
|
||||||
|
a.createFile("~/exampleStoreFolder/.gpg-id", fpr)
|
||||||
|
|
||||||
|
a.configureGpgKeys(KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())), true)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val res = prov.task("test_installAndConfigureGopassAndMountStore") {
|
val res = a.installGopass()
|
||||||
installGopass()
|
val res2 = a.configureGopass(a.userHome() + gopassRootDir)
|
||||||
configureGopass(prov.userHome() + gopassRootDir)
|
val res3 = a.gopassMountStore("exampleStore", "~/exampleStoreFolder")
|
||||||
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
|
// then
|
||||||
prov.fileContent("~/.config/gopass/config") // displays the content in the logs
|
a.fileContent("~/.config/gopass/config.yml") // displays the content in the logs
|
||||||
assertTrue(res.success)
|
assertTrue(res.success)
|
||||||
assertTrue(prov.fileContainsText("~/.config/gopass/config", "/home/testuser/.password-store"))
|
assertTrue(res2.success)
|
||||||
assertTrue(prov.fileContainsText("~/.config/gopass/config", "exampleStore"))
|
assertTrue(res3.success)
|
||||||
assertTrue(prov.checkDir(".git", gopassRootDir))
|
assertTrue(a.fileContainsText("~/.config/gopass/config.yml", "/home/testuser/.password-store"))
|
||||||
|
assertTrue(a.fileContainsText("~/.config/gopass/config.yml", "exampleStore"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -65,10 +74,10 @@ internal class GopassKtTest {
|
||||||
val privateKey = GopassSecretSource("path-to/priv.key").secret()
|
val privateKey = GopassSecretSource("path-to/priv.key").secret()
|
||||||
|
|
||||||
// given
|
// given
|
||||||
val prov = remote(host, user)
|
val a = remote(host, user)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val res = prov.task {
|
val res = a.task {
|
||||||
configureGpgKeys(
|
configureGpgKeys(
|
||||||
KeyPair(
|
KeyPair(
|
||||||
pubKey,
|
pubKey,
|
||||||
|
@ -84,12 +93,11 @@ internal class GopassKtTest {
|
||||||
cmd("printf \"\\ntest\\ntest@test.org\\n\" | gopass init " + gpgFingerprint(pubKey.plain())) // gopass init in default location with gpg-key-fingerprint of given key
|
cmd("printf \"\\ntest\\ntest@test.org\\n\" | gopass init " + gpgFingerprint(pubKey.plain())) // gopass init in default location with gpg-key-fingerprint of given key
|
||||||
}
|
}
|
||||||
downloadGopassBridge()
|
downloadGopassBridge()
|
||||||
installGopassJsonApi()
|
installGopassBridgeJsonApi()
|
||||||
configureGopassJsonApi()
|
configureGopassBridgeJsonApi()
|
||||||
}
|
}
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(res.success)
|
assertTrue(res.success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ internal class K3SDesktopConfigRepositoryKtTest {
|
||||||
@Test
|
@Test
|
||||||
fun getConfig_fails_due_to_missing_file() {
|
fun getConfig_fails_due_to_missing_file() {
|
||||||
val exception = assertThrows<FileNotFoundException> {
|
val exception = assertThrows<FileNotFoundException> {
|
||||||
getConfig("src/test/resources/Idonotexist.yaml")
|
getK3sConfig(ConfigFileName("src/test/resources/Idonotexist.yaml"))
|
||||||
}
|
}
|
||||||
assertEquals(FileNotFoundException::class.java, exception.javaClass)
|
assertEquals(FileNotFoundException::class.java, exception.javaClass)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,14 @@ import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
|
||||||
internal class NextcloudClientTest {
|
class MsTeamsKtTest {
|
||||||
@ExtensiveContainerTest
|
|
||||||
fun test_installNextcloudClient() {
|
|
||||||
// when
|
|
||||||
val res = defaultTestContainer().installNextcloudClient()
|
|
||||||
|
|
||||||
|
@ExtensiveContainerTest
|
||||||
|
fun installMsTeams() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
// when
|
||||||
|
val res = a.task { installMsTeams() }
|
||||||
// then
|
// then
|
||||||
assertTrue(res.success)
|
assertTrue(res.success)
|
||||||
}
|
}
|
|
@ -2,12 +2,25 @@ package org.domaindrivenarchitecture.provs.desktop.infrastructure
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Disabled
|
import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
internal class VSCodeKtTest {
|
internal class VSCodeKtTest {
|
||||||
|
|
||||||
|
@ExtensiveContainerTest
|
||||||
|
fun provisionAdditionalTools() {
|
||||||
|
// given
|
||||||
|
defaultTestContainer().aptInstall("curl unzip")
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = defaultTestContainer().provisionAdditionalToolsForVSCode()
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled("Test currently not working, needs fix. VSC is installed by snapd which is not currently supported to run inside docker")
|
@Disabled("Test currently not working, needs fix. VSC is installed by snapd which is not currently supported to run inside docker")
|
||||||
fun installVSC() {
|
fun installVSC() {
|
||||||
|
|
|
@ -254,12 +254,12 @@ internal class ProvTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// additional methods to be used in the tests below
|
// given
|
||||||
fun Prov.checkPrereq_evaluateToFailure() = requireLast {
|
fun Prov.checkPrereq_evaluateToFailure() = requireLast {
|
||||||
ProvResult(false, err = "This is a test error.")
|
ProvResult(false, err = "This is a test error.")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.testMethodForOutputTest_with_mode_requireLast() = requireLast {
|
fun Prov.methodThatProvidesSomeOutput() = requireLast {
|
||||||
|
|
||||||
if (!checkPrereq_evaluateToFailure().success) {
|
if (!checkPrereq_evaluateToFailure().success) {
|
||||||
sh(
|
sh(
|
||||||
|
@ -273,17 +273,6 @@ internal class ProvTest {
|
||||||
sh("echo -End test-")
|
sh("echo -End test-")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Prov.testMethodForOutputTest_nested_with_failure() = taskWithResult {
|
|
||||||
|
|
||||||
taskWithResult(name = "sub1") {
|
|
||||||
taskWithResult {
|
|
||||||
ProvResult(true)
|
|
||||||
}
|
|
||||||
ProvResult(false, err = "Iamanerrormessage")
|
|
||||||
}
|
|
||||||
cmd("echo -End test-")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@NonCi
|
@NonCi
|
||||||
fun prov_prints_correct_output_for_overall_success() {
|
fun prov_prints_correct_output_for_overall_success() {
|
||||||
|
@ -301,9 +290,7 @@ internal class ProvTest {
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.NONE)
|
Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.NONE)
|
||||||
.session {
|
.methodThatProvidesSomeOutput()
|
||||||
testMethodForOutputTest_with_mode_requireLast()
|
|
||||||
}
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
System.setOut(originalOut)
|
System.setOut(originalOut)
|
||||||
|
@ -313,14 +300,13 @@ internal class ProvTest {
|
||||||
|
|
||||||
val expectedOutput =
|
val expectedOutput =
|
||||||
"============================================== SUMMARY (test instance with no progress info) =============================================\n" +
|
"============================================== SUMMARY (test instance with no progress info) =============================================\n" +
|
||||||
"> \u001B[92mSuccess\u001B[0m -- session \n" +
|
"> \u001B[92mSuccess\u001B[0m -- methodThatProvidesSomeOutput (requireLast) \n" +
|
||||||
"---> \u001B[92mSuccess\u001B[0m -- testMethodForOutputTest_with_mode_requireLast (requireLast) \n" +
|
"---> \u001B[93mFAILED\u001B[0m -- checkPrereq_evaluateToFailure (requireLast) -- Error: This is a test error.\n" +
|
||||||
"------> \u001B[93mFAILED\u001B[0m -- checkPrereq_evaluateToFailure (requireLast) -- Error: This is a test error.\n" +
|
"---> \u001B[92mSuccess\u001B[0m -- sh \n" +
|
||||||
"------> \u001B[92mSuccess\u001B[0m -- sh \n" +
|
"------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo -Start test-]\n" +
|
||||||
"---------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo -Start test-]\n" +
|
"------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo Some output]\n" +
|
||||||
"---------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo Some output]\n" +
|
"---> \u001B[92mSuccess\u001B[0m -- sh \n" +
|
||||||
"------> \u001B[92mSuccess\u001B[0m -- sh \n" +
|
"------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo -End test-]\n" +
|
||||||
"---------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo -End test-]\n" +
|
|
||||||
"----------------------------------------------------------------------------------------------------\n" +
|
"----------------------------------------------------------------------------------------------------\n" +
|
||||||
"Overall > \u001B[92mSuccess\u001B[0m\n" +
|
"Overall > \u001B[92mSuccess\u001B[0m\n" +
|
||||||
"============================================ SUMMARY END ===========================================\n" +
|
"============================================ SUMMARY END ===========================================\n" +
|
||||||
|
@ -331,7 +317,7 @@ internal class ProvTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@NonCi
|
@NonCi
|
||||||
fun prov_prints_correct_output_for_nested_calls_with_failure() {
|
fun prov_prints_correct_output_for_failure() {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
setRootLoggingLevel(Level.OFF)
|
setRootLoggingLevel(Level.OFF)
|
||||||
|
@ -345,9 +331,8 @@ internal class ProvTest {
|
||||||
System.setErr(PrintStream(errContent))
|
System.setErr(PrintStream(errContent))
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.NONE).session {
|
Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.NONE)
|
||||||
testMethodForOutputTest_nested_with_failure()
|
.checkPrereq_evaluateToFailure()
|
||||||
}
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
System.setOut(originalOut)
|
System.setOut(originalOut)
|
||||||
|
@ -357,14 +342,7 @@ internal class ProvTest {
|
||||||
|
|
||||||
val expectedOutput =
|
val expectedOutput =
|
||||||
"============================================== SUMMARY (test instance with no progress info) =============================================\n" +
|
"============================================== SUMMARY (test instance with no progress info) =============================================\n" +
|
||||||
"> \u001B[91mFAILED\u001B[0m -- session \n" +
|
"> \u001B[91mFAILED\u001B[0m -- checkPrereq_evaluateToFailure (requireLast) -- Error: This is a test error.\n" +
|
||||||
"---> \u001B[91mFAILED\u001B[0m -- testMethodForOutputTest_nested_with_failure \n" +
|
|
||||||
"------> \u001B[91mFAILED\u001B[0m -- sub1 \n" +
|
|
||||||
"---------> \u001B[92mSuccess\u001B[0m -- testMethodForOutputTest_nested_with_failure \n" +
|
|
||||||
"---------> \u001B[91mFAILED\u001B[0m -- <<returned result>> -- Error: Iamanerrormessage\n" +
|
|
||||||
"------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo -End test-]\n" +
|
|
||||||
"----------------------------------------------------------------------------------------------------\n" +
|
|
||||||
"Overall > \u001B[91mFAILED\u001B[0m \n" +
|
|
||||||
"============================================ SUMMARY END ===========================================\n" +
|
"============================================ SUMMARY END ===========================================\n" +
|
||||||
"\n"
|
"\n"
|
||||||
|
|
||||||
|
@ -626,10 +604,9 @@ internal class ProvTest {
|
||||||
val prov = Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.NONE)
|
val prov = Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.NONE)
|
||||||
|
|
||||||
// when
|
// when
|
||||||
prov.session {
|
prov.task {
|
||||||
addInfoText("Text1")
|
addInfoText("Text1")
|
||||||
addInfoText("Text2\nwith newline")
|
addInfoText("Text2\nwith newline")
|
||||||
ProvResult(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// then
|
// then
|
||||||
|
@ -640,7 +617,7 @@ internal class ProvTest {
|
||||||
|
|
||||||
val expectedOutput =
|
val expectedOutput =
|
||||||
"============================================== SUMMARY (test instance with no progress info) =============================================\n" +
|
"============================================== SUMMARY (test instance with no progress info) =============================================\n" +
|
||||||
"> \u001B[92mSuccess\u001B[0m -- session \n" +
|
"> \u001B[92mSuccess\u001B[0m -- infoText_is_printed_correctly \n" +
|
||||||
"+++++++++++++++++++++++++++++++++++ \u001B[94mAdditional information\u001B[0m +++++++++++++++++++++++++++++++++++++++\n" +
|
"+++++++++++++++++++++++++++++++++++ \u001B[94mAdditional information\u001B[0m +++++++++++++++++++++++++++++++++++++++\n" +
|
||||||
"Text1\n" +
|
"Text1\n" +
|
||||||
"Text2\n" +
|
"Text2\n" +
|
||||||
|
@ -652,171 +629,5 @@ internal class ProvTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// method to be used in the next test
|
|
||||||
fun Prov.testMethodForOutputTest_with_returned_results() = taskWithResult {
|
|
||||||
|
|
||||||
taskWithResult(name = "sub1") {
|
|
||||||
taskWithResult("sub2a") {
|
|
||||||
ProvResult(true)
|
|
||||||
}
|
|
||||||
taskWithResult("sub2b") {
|
|
||||||
ProvResult(false, err = "error msg A for sub2b should be shown as result of sub2b")
|
|
||||||
}
|
|
||||||
optional("sub2c-optional") {
|
|
||||||
taskWithResult("sub3a-taskWithResult") {
|
|
||||||
addResultToEval(
|
|
||||||
ProvResult(
|
|
||||||
false,
|
|
||||||
err = "returned-result - error msg B should be once in output - in addResultToEval"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requireLast("sub2d-requireLast") {
|
|
||||||
taskWithResult("sub3b-taskWithResult without error message") {
|
|
||||||
ProvResult(false) // no error message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
task("sub2e-task") {
|
|
||||||
addResultToEval(ProvResult(true))
|
|
||||||
ProvResult(
|
|
||||||
false,
|
|
||||||
err = "error should NOT be in output as results of task (not taskWithResult) are ignored"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
taskWithResult("sub2f-taskWithResult") {
|
|
||||||
ProvResult(
|
|
||||||
false,
|
|
||||||
err = "returned-result - error msg C should be once in output - at the end of sub3taskWithResult "
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ProvResult(false, err = "returned-result - error msg D should be once in output - at the end of sub1 ")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@NonCi
|
|
||||||
fun prov_prints_correct_output_for_returned_results() {
|
|
||||||
|
|
||||||
// given
|
|
||||||
setRootLoggingLevel(Level.OFF)
|
|
||||||
|
|
||||||
val outContent = ByteArrayOutputStream()
|
|
||||||
val errContent = ByteArrayOutputStream()
|
|
||||||
val originalOut = System.out
|
|
||||||
val originalErr = System.err
|
|
||||||
|
|
||||||
System.setOut(PrintStream(outContent))
|
|
||||||
System.setErr(PrintStream(errContent))
|
|
||||||
|
|
||||||
// when
|
|
||||||
Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.NONE)
|
|
||||||
.testMethodForOutputTest_with_returned_results()
|
|
||||||
|
|
||||||
// then
|
|
||||||
System.setOut(originalOut)
|
|
||||||
System.setErr(originalErr)
|
|
||||||
|
|
||||||
println(outContent.toString())
|
|
||||||
|
|
||||||
val expectedOutput =
|
|
||||||
"============================================== SUMMARY (test instance with no progress info) =============================================\n" +
|
|
||||||
"> \u001B[91mFAILED\u001B[0m -- testMethodForOutputTest_with_returned_results \n" +
|
|
||||||
"---> \u001B[91mFAILED\u001B[0m -- sub1 \n" +
|
|
||||||
"------> \u001B[92mSuccess\u001B[0m -- sub2a \n" +
|
|
||||||
"------> \u001B[91mFAILED\u001B[0m -- sub2b -- Error: error msg A for sub2b should be shown as result of sub2b\n" +
|
|
||||||
"------> \u001B[92mSuccess\u001B[0m -- sub2c-optional \n" +
|
|
||||||
"---------> \u001B[93mFAILED\u001B[0m -- sub3a-taskWithResult \n" +
|
|
||||||
"------------> \u001B[93mFAILED\u001B[0m -- addResultToEval -- Error: returned-result - error msg B should be once in output - in addResultToEval\n" +
|
|
||||||
"------> \u001B[91mFAILED\u001B[0m -- sub2d-requireLast \n" +
|
|
||||||
"---------> \u001B[91mFAILED\u001B[0m -- sub3b-taskWithResult without error message \n" +
|
|
||||||
"------> \u001B[92mSuccess\u001B[0m -- sub2e-task \n" +
|
|
||||||
"---------> \u001B[92mSuccess\u001B[0m -- addResultToEval \n" +
|
|
||||||
"------> \u001B[91mFAILED\u001B[0m -- sub2f-taskWithResult -- Error: returned-result - error msg C should be once in output - at the end of sub3taskWithResult \n" +
|
|
||||||
"------> \u001B[91mFAILED\u001B[0m -- <<returned result>> -- Error: returned-result - error msg D should be once in output - at the end of sub1 \n" +
|
|
||||||
"----------------------------------------------------------------------------------------------------\n" +
|
|
||||||
"Overall > \u001B[91mFAILED\u001B[0m \n" +
|
|
||||||
"============================================ SUMMARY END ===========================================\n" +
|
|
||||||
"\n"
|
|
||||||
|
|
||||||
assertEquals(expectedOutput, outContent.toString().replace("\r", ""))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun session_on_top_level_succeeds() {
|
|
||||||
// when
|
|
||||||
val result = Prov.newInstance().session { cmd("echo bla") }
|
|
||||||
// then
|
|
||||||
assertTrue(result.success)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun session_not_on_top_level_throws_an_exception() {
|
|
||||||
// when
|
|
||||||
val exception = org.junit.jupiter.api.assertThrows<RuntimeException> {
|
|
||||||
local().session {
|
|
||||||
session {
|
|
||||||
cmd("echo bla")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// then
|
|
||||||
assertEquals(
|
|
||||||
"A session can only be created on the top-level and may not be included in another session or task.",
|
|
||||||
exception.message
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// method for task_warning_for_task_on_top_level_is_in_output
|
|
||||||
// must be declared outside test task_warning_for_task_on_top_level_is_in_output in order to avoid strange naming in result output
|
|
||||||
fun Prov.tst_task() = task {
|
|
||||||
task_returningTrue()
|
|
||||||
task_returningFalse()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun task_warning_for_task_on_top_level_is_in_output() {
|
|
||||||
// given
|
|
||||||
setRootLoggingLevel(Level.OFF)
|
|
||||||
|
|
||||||
val outContent = ByteArrayOutputStream()
|
|
||||||
val errContent = ByteArrayOutputStream()
|
|
||||||
val originalOut = System.out
|
|
||||||
val originalErr = System.err
|
|
||||||
|
|
||||||
System.setOut(PrintStream(outContent))
|
|
||||||
System.setErr(PrintStream(errContent))
|
|
||||||
|
|
||||||
// when
|
|
||||||
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.BASIC)
|
|
||||||
.tst_task().success // test that also second run gets warning
|
|
||||||
|
|
||||||
// then
|
|
||||||
System.setOut(originalOut)
|
|
||||||
System.setErr(originalErr)
|
|
||||||
|
|
||||||
println(outContent.toString())
|
|
||||||
|
|
||||||
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" +
|
|
||||||
"---> \u001B[91mFAILED\u001B[0m -- task_returningFalse \n" +
|
|
||||||
"----------------------------------------------------------------------------------------------------\n" +
|
|
||||||
"Overall > \u001B[91mFAILED\u001B[0m \n" +
|
|
||||||
"============================================ SUMMARY END ===========================================\n" +
|
|
||||||
"\n"
|
|
||||||
|
|
||||||
val expectedOutputDoubleRun = expectedOutputOneRun + expectedOutputOneRun
|
|
||||||
|
|
||||||
assertEquals(expectedOutputDoubleRun, outContent.toString().replace("\r", ""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
package org.domaindrivenarchitecture.provs.framework.core.processors
|
package org.domaindrivenarchitecture.provs.framework.core.processors
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.*
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.platforms.SHELL
|
import org.domaindrivenarchitecture.provs.framework.core.platforms.SHELL
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.makeCurrentUserSudoerWithoutPasswordRequired
|
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
|
||||||
import org.domaindrivenarchitecture.provs.test.testDockerWithSudo
|
import org.domaindrivenarchitecture.provs.test.testDockerWithSudo
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
|
||||||
|
|
||||||
val DEFAULT_START_MODE_TEST_CONTAINER = ContainerStartMode.USE_RUNNING_ELSE_CREATE
|
val DEFAULT_START_MODE_TEST_CONTAINER = ContainerStartMode.USE_RUNNING_ELSE_CREATE
|
||||||
|
|
||||||
|
@ -27,42 +22,4 @@ class ContainerUbuntuHostProcessorTest {
|
||||||
assertEquals(0, res.exitCode)
|
assertEquals(0, res.exitCode)
|
||||||
assertEquals("abc", res.out)
|
assertEquals("abc", res.out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ExtensiveContainerTest
|
|
||||||
fun test_reopeing_ssh_session_succeeds() {
|
|
||||||
|
|
||||||
// given
|
|
||||||
val containerName = "prov-test-ssh-with-container"
|
|
||||||
val password = Secret("testuserpw")
|
|
||||||
|
|
||||||
val prov = Prov.newInstance(
|
|
||||||
ContainerUbuntuHostProcessor(
|
|
||||||
containerName,
|
|
||||||
startMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
|
||||||
sudo = true,
|
|
||||||
dockerImage = "ubuntu_plus_user",
|
|
||||||
options = "--expose=22"
|
|
||||||
),
|
|
||||||
progressType = ProgressType.NONE
|
|
||||||
)
|
|
||||||
prov.task {
|
|
||||||
makeCurrentUserSudoerWithoutPasswordRequired(password)
|
|
||||||
aptInstall("openssh-server")
|
|
||||||
cmd("sudo service ssh start")
|
|
||||||
}
|
|
||||||
|
|
||||||
val ipOfContainer = local().cmd("sudo docker inspect -f \"{{ .NetworkSettings.IPAddress }}\" $containerName").out?.trim()
|
|
||||||
?: throw IllegalStateException("Ip not found")
|
|
||||||
val remoteProvBySsh = remote(ipOfContainer, "testuser", password)
|
|
||||||
|
|
||||||
// when
|
|
||||||
val firstSessionResult = remoteProvBySsh.cmd("echo 1")
|
|
||||||
val secondSessionResult = remoteProvBySsh.cmd("echo 1")
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertTrue(firstSessionResult.success)
|
|
||||||
assertTrue(secondSessionResult.success)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -5,7 +5,10 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.creat
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.fileContent
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.fileContent
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.*
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.*
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSourceType
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.SecretSourceType
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.*
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.configureUser
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.createUser
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.userExists
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.userIsInGroupSudo
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||||
|
@ -55,24 +58,24 @@ internal class ProvisionUserKtTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ContainerTest
|
@ContainerTest
|
||||||
fun createUserWithSudoAndCopiedSshKey() {
|
fun createUserWithSudo() {
|
||||||
// given
|
// given
|
||||||
val prov = defaultTestContainer()
|
val a = defaultTestContainer()
|
||||||
val newUser = "testnewsudouser4"
|
val newUser = "testnewsudouser3"
|
||||||
prov.task {
|
a.task {
|
||||||
createDir(".ssh")
|
createDir(".ssh")
|
||||||
createFile("~/.ssh/authorized_keys", "newdummykey")
|
createFile("~/.ssh/authorized_keys", "newdummykey")
|
||||||
}
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val res = prov.createUser(newUser, userCanSudoWithoutPassword = true, copyAuthorizedSshKeysFromCurrentUser = true)
|
val res = a.createUser(newUser, sudo = true, copyAuthorizedSshKeysFromCurrentUser = true)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(res.success)
|
assertTrue(res.success)
|
||||||
assertTrue(prov.userExists(newUser))
|
assertTrue(a.userExists(newUser))
|
||||||
assertEquals("newdummykey", prov.fileContent("/home/$newUser/.ssh/authorized_keys", sudo = true))
|
assertEquals("newdummykey", a.fileContent("/home/$newUser/.ssh/authorized_keys", sudo = true))
|
||||||
|
|
||||||
// new user can sudo
|
// new user can sudo
|
||||||
assertTrue(prov.cmd("sudo -H -u $newUser bash -c 'sudo echo \"I am \$USER, with uid \$UID\"' ").success)
|
assertTrue(a.cmd("sudo -H -u $newUser bash -c 'sudo echo \"I am \$USER, with uid \$UID\"' ").success)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -45,7 +45,7 @@ internal class CliArgumentParserTest {
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertTrue(result.isValidTarget())
|
assertTrue(result.isValidTarget())
|
||||||
assertEquals(ApplicationFileName("app.yaml").fullyQualifiedName(), result.applicationFileName?.fullyQualifiedName())
|
assertEquals(ApplicationFileName("app.yaml"), result.applicationFileName)
|
||||||
assertEquals(TargetCliCommand("user@host.com"), result.target)
|
assertEquals(TargetCliCommand("user@host.com"), result.target)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,44 +0,0 @@
|
||||||
package org.domaindrivenarchitecture.provs.server.domain
|
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
|
||||||
import org.domaindrivenarchitecture.provs.configuration.infrastructure.DefaultConfigFileRepository
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.getLocalFileContent
|
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFile
|
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFileName
|
|
||||||
import org.domaindrivenarchitecture.provs.server.infrastructure.DefaultApplicationFileRepository
|
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.assertThrows
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
internal class ApplicationFileKtTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun assertValidateReturnsSpecErrors() {
|
|
||||||
// given
|
|
||||||
val applicationFileName = ApplicationFileName("src/test/resources/failed-spec.yaml")
|
|
||||||
|
|
||||||
// when
|
|
||||||
val file = ApplicationFile(applicationFileName, getLocalFileContent(applicationFileName.fullyQualifiedName()))
|
|
||||||
|
|
||||||
// then
|
|
||||||
val result = file.validate()
|
|
||||||
|
|
||||||
assertEquals(arrayListOf("Spec failed"), result)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun assertValidateReturnsJavaErrors() {
|
|
||||||
// given
|
|
||||||
val applicationFileName = ApplicationFileName("src/test/resources/java-exception.yaml")
|
|
||||||
|
|
||||||
// when
|
|
||||||
val file = ApplicationFile(applicationFileName, getLocalFileContent(applicationFileName.fullyQualifiedName()))
|
|
||||||
|
|
||||||
// then
|
|
||||||
val result = file.validate()
|
|
||||||
|
|
||||||
assertEquals(arrayListOf("Exception in thread"), result)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,8 +2,6 @@ package org.domaindrivenarchitecture.provs.server.infrastructure
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
||||||
import org.domaindrivenarchitecture.provs.configuration.infrastructure.DefaultConfigFileRepository
|
import org.domaindrivenarchitecture.provs.configuration.infrastructure.DefaultConfigFileRepository
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFile
|
|
||||||
import org.domaindrivenarchitecture.provs.server.domain.k3s.ApplicationFileName
|
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
@ -14,32 +12,30 @@ internal class DefaultApplicationFileRepositoryKtTest {
|
||||||
@Test
|
@Test
|
||||||
fun assertExistsThrowsRuntimeException() {
|
fun assertExistsThrowsRuntimeException() {
|
||||||
// when
|
// when
|
||||||
val invalidFileName = ApplicationFileName("iDontExist")
|
val invalidFileName = ConfigFileName("iDontExist")
|
||||||
val repo = DefaultApplicationFileRepository(invalidFileName)
|
val repo = DefaultConfigFileRepository()
|
||||||
|
|
||||||
// then
|
// then
|
||||||
val exception = assertThrows<RuntimeException>(
|
val exception = assertThrows<RuntimeException>(
|
||||||
"Should not find the file."
|
"Should not find the file."
|
||||||
) { repo.getFile() }
|
) { repo.assertExists(invalidFileName) }
|
||||||
|
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"Application file not found. Please check if path is correct.",
|
"Config file iDontExist not found. Please check if path is correct.",
|
||||||
exception.message)
|
exception.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun assertGetFileThrowsRuntimeException() {
|
fun assertExistsPasses() {
|
||||||
|
// given
|
||||||
|
val validFileName = "src/test/resources/existing_file"
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val invalidFileName = ApplicationFileName("src/test/resources/java-exception.yaml")
|
val validFile = ConfigFileName(File(validFileName).path)
|
||||||
val repo = DefaultApplicationFileRepository(invalidFileName)
|
val repo = DefaultConfigFileRepository()
|
||||||
|
repo.assertExists(validFile)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
val exception = assertThrows<RuntimeException>(
|
// no exception is thrown
|
||||||
"Should not find the file."
|
|
||||||
) { repo.getFile() }
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
"Application file was invalid.",
|
|
||||||
exception.message)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,9 +1,7 @@
|
||||||
package org.domaindrivenarchitecture.provs.server.infrastructure
|
package org.domaindrivenarchitecture.provs.server.infrastructure
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.checkFile
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.checkFile
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.deleteFile
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.fileContainsText
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.fileContainsText
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
@ -14,21 +12,15 @@ class SshKtTest {
|
||||||
fun test_configureSsh() {
|
fun test_configureSsh() {
|
||||||
|
|
||||||
// given
|
// given
|
||||||
val prov = defaultTestContainer()
|
val p = defaultTestContainer()
|
||||||
prov.task {
|
|
||||||
aptInstall("openssh-server")
|
|
||||||
deleteFile(pathSshdHardeningConfig, sudo = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// when
|
// when
|
||||||
prov.configureSsh()
|
val res = p.configureSsh()
|
||||||
|
|
||||||
// then
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
// Note: result of method configureSsh might have status failure as restart ssh within docker is not possible,
|
assertTrue(p.fileContainsText("/etc/ssh/ssh_config","PasswordAuthentication no", sudo=true))
|
||||||
// but files should have expected content
|
assertTrue(p.fileContainsText("/etc/ssh/sshd_config","PasswordAuthentication no", sudo=true))
|
||||||
assertTrue(prov.fileContainsText("/etc/ssh/ssh_config","PasswordAuthentication no", sudo=true))
|
assertTrue(p.checkFile("/etc/ssh/sshd_config.d/sshd_hardening.conf"))
|
||||||
assertTrue(prov.fileContainsText("/etc/ssh/sshd_config","PasswordAuthentication no", sudo=true))
|
|
||||||
assertTrue(prov.checkFile("/etc/ssh/sshd_config.d/sshd_hardening.conf"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,7 +3,10 @@ package org.domaindrivenarchitecture.provs.syspec.infrastructure
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.ProvResult
|
import org.domaindrivenarchitecture.provs.framework.core.ProvResult
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createDirs
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createDirs
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFile
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.createFile
|
||||||
import org.domaindrivenarchitecture.provs.syspec.domain.*
|
import org.domaindrivenarchitecture.provs.syspec.domain.FileSpec
|
||||||
|
import org.domaindrivenarchitecture.provs.syspec.domain.FolderSpec
|
||||||
|
import org.domaindrivenarchitecture.provs.syspec.domain.SocketSpec
|
||||||
|
import org.domaindrivenarchitecture.provs.syspec.domain.SyspecConfig
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
import org.domaindrivenarchitecture.provs.test.testLocal
|
import org.domaindrivenarchitecture.provs.test.testLocal
|
||||||
|
@ -128,30 +131,4 @@ internal class VerificationKtTest {
|
||||||
assertFalse(res3)
|
assertFalse(res3)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ContainerTest
|
|
||||||
fun test_verifySpecConfig_succeeds() {
|
|
||||||
// given
|
|
||||||
val dir = "/home/testuser"
|
|
||||||
val prov = defaultTestContainer()
|
|
||||||
|
|
||||||
// when
|
|
||||||
val res = prov.verifySpecConfig(SyspecConfig(folder = listOf(FolderSpec(dir)), command = listOf(CommandSpec("echo bla"))))
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertTrue(res.success)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ContainerTest
|
|
||||||
fun test_verifySpecConfig_fails() {
|
|
||||||
// given
|
|
||||||
val dir = "/home/testuser"
|
|
||||||
val prov = defaultTestContainer()
|
|
||||||
|
|
||||||
// when
|
|
||||||
val res = prov.verifySpecConfig(SyspecConfig(command = listOf(CommandSpec("echoo bla"), CommandSpec("echo bla")), folder = listOf(FolderSpec(dir))))
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertFalse(res.success)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
6
src/test/resources/desktop-config-example.json
Normal file
6
src/test/resources/desktop-config-example.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"ssh": null,
|
||||||
|
"gpg": null,
|
||||||
|
"gitUserName": "mygitusername",
|
||||||
|
"gitEmail": "my@git.email"
|
||||||
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
-- Spec failed --------------------
|
|
||||||
|
|
||||||
{:jvb-auth-password 1234,
|
|
||||||
^^^^
|
|
||||||
:jicofo-auth-password ...,
|
|
||||||
:jicofo-component-secret ...}
|
|
||||||
|
|
||||||
should satisfy
|
|
||||||
|
|
||||||
bash-env-string?
|
|
||||||
|
|
||||||
-------------------------
|
|
||||||
Detected 1 error
|
|
|
@ -1,34 +0,0 @@
|
||||||
Exception in thread "main" java.lang.IllegalArgumentException: Cannot open <nil> as a Reader.
|
|
||||||
at clojure.java.io$fn__11641.invokeStatic(io.clj:288)
|
|
||||||
at clojure.java.io$fn__11641.invoke(io.clj:288)
|
|
||||||
at clojure.java.io$fn__11530$G__11519__11537.invoke(io.clj:69)
|
|
||||||
at clojure.java.io$reader.invokeStatic(io.clj:102)
|
|
||||||
at clojure.java.io$reader.doInvoke(io.clj:86)
|
|
||||||
at clojure.lang.RestFn.invoke(RestFn.java:410)
|
|
||||||
at clojure.lang.AFn.applyToHelper(AFn.java:154)
|
|
||||||
at clojure.lang.RestFn.applyTo(RestFn.java:132)
|
|
||||||
at clojure.core$apply.invokeStatic(core.clj:669)
|
|
||||||
at clojure.core$slurp.invokeStatic(core.clj:7009)
|
|
||||||
at clojure.core$slurp.doInvoke(core.clj:7009)
|
|
||||||
at clojure.lang.RestFn.invoke(RestFn.java:410)
|
|
||||||
at dda.c4k_common.yaml$fn__577.invokeStatic(yaml.clj:39)
|
|
||||||
at dda.c4k_common.yaml$fn__577.invoke(yaml.clj:38)
|
|
||||||
at clojure.lang.MultiFn.invoke(MultiFn.java:229)
|
|
||||||
at dda.c4k_common.yaml$load_as_edn.invokeStatic(yaml.clj:42)
|
|
||||||
at dda.c4k_common.yaml$load_as_edn.invoke(yaml.clj:41)
|
|
||||||
at dda.c4k_website.website$replace_common_data.invokeStatic(website.cljc:122)
|
|
||||||
at dda.c4k_website.website$replace_common_data.invoke(website.cljc:117)
|
|
||||||
at dda.c4k_website.website$generate_hashfile_volume.invokeStatic(website.cljc:198)
|
|
||||||
at dda.c4k_website.website$generate_hashfile_volume.invoke(website.cljc:196)
|
|
||||||
at dda.c4k_website.core$generate_configs.invokeStatic(core.cljc:60)
|
|
||||||
at dda.c4k_website.core$generate_configs.invoke(core.cljc:45)
|
|
||||||
at dda.c4k_website.core$k8s_objects.invokeStatic(core.cljc:74)
|
|
||||||
at dda.c4k_website.core$k8s_objects.invoke(core.cljc:66)
|
|
||||||
at dda.c4k_common.common$generate_common.invokeStatic(common.cljc:68)
|
|
||||||
at dda.c4k_common.common$generate_common.invoke(common.cljc:60)
|
|
||||||
at dda.c4k_common.uberjar$main_common.invokeStatic(uberjar.clj:50)
|
|
||||||
at dda.c4k_common.uberjar$main_common.invoke(uberjar.clj:31)
|
|
||||||
at dda.c4k_website.uberjar$_main.invokeStatic(uberjar.clj:9)
|
|
||||||
at dda.c4k_website.uberjar$_main.doInvoke(uberjar.clj:8)
|
|
||||||
at clojure.lang.RestFn.applyTo(RestFn.java:137)
|
|
||||||
at dda.c4k_website.uberjar.main(Unknown Source)
|
|
|
@ -1,9 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: config
|
|
||||||
type: Opaque
|
|
||||||
data:
|
|
||||||
JVB_AUTH_PASSWORD: BLA
|
|
||||||
JICOFO_AUTH_PASSWORD: BLA
|
|
||||||
JICOFO_COMPONENT_SECRET: BLA
|
|
|
@ -2,7 +2,6 @@ package org.domaindrivenarchitecture.provs.test
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.ProgressType
|
import org.domaindrivenarchitecture.provs.framework.core.ProgressType
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.docker.containerRuns
|
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.docker.dockerImageExists
|
import org.domaindrivenarchitecture.provs.framework.core.docker.dockerImageExists
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.docker.dockerProvideImage
|
import org.domaindrivenarchitecture.provs.framework.core.docker.dockerProvideImage
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.docker.dockerimages.UbuntuPlusUser
|
import org.domaindrivenarchitecture.provs.framework.core.docker.dockerimages.UbuntuPlusUser
|
||||||
|
@ -16,7 +15,7 @@ const val defaultTestContainerName = "provs_test"
|
||||||
private lateinit var prov: Prov
|
private lateinit var prov: Prov
|
||||||
|
|
||||||
fun defaultTestContainer(startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE): Prov {
|
fun defaultTestContainer(startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE): Prov {
|
||||||
if (!::prov.isInitialized || !testLocal().containerRuns(defaultTestContainerName) || (startMode == ContainerStartMode.CREATE_NEW_KILL_EXISTING)) { prov = initDefaultTestContainer(startMode) }
|
if (!::prov.isInitialized) { prov = initDefaultTestContainer(startMode) }
|
||||||
return prov
|
return prov
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
private const val CONTAINER_TEST = "containertest"
|
private const val CONTAINER_TEST = "containertest"
|
||||||
private const val EXTENSIVE_CONTAINER_TEST = "extensivecontainertest"
|
private const val EXTENSIVE_CONTAINER_TEST = "extensivecontainertest"
|
||||||
private const val NON_CI = "nonci"
|
private const val CONTAINER_TEST_NON_CI = "containernonci"
|
||||||
|
|
||||||
|
|
||||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
|
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
|
||||||
|
@ -28,7 +28,6 @@ annotation class ExtensiveContainerTest
|
||||||
|
|
||||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
|
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
|
||||||
@Retention
|
@Retention
|
||||||
@Tag(NON_CI)
|
@Tag(CONTAINER_TEST_NON_CI)
|
||||||
@Test
|
@Test
|
||||||
// For test which do not run in ci pipeline
|
|
||||||
annotation class NonCi
|
annotation class NonCi
|
Loading…
Reference in a new issue