refactor imports to org.domaindrivenarchitecture
This commit is contained in:
parent
3810fe6723
commit
728e2a8d8f
101 changed files with 6288 additions and 0 deletions
46
.classpath
Normal file
46
.classpath
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="bin/main" path="src/main/kotlin">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="gradle_scope" value="main"/>
|
||||||
|
<attribute name="gradle_used_by_scope" value="main"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="bin/main" path="src/main/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="gradle_scope" value="main"/>
|
||||||
|
<attribute name="gradle_used_by_scope" value="main"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="bin/test" path="src/test/kotlin">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="gradle_scope" value="test"/>
|
||||||
|
<attribute name="gradle_used_by_scope" value="test"/>
|
||||||
|
<attribute name="test" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="bin/test" path="src/test/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="gradle_scope" value="test"/>
|
||||||
|
<attribute name="gradle_used_by_scope" value="test"/>
|
||||||
|
<attribute name="test" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="bin/testFixtures" path="src/testFixtures/kotlin">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="gradle_scope" value="testFixtures"/>
|
||||||
|
<attribute name="gradle_used_by_scope" value="testFixtures"/>
|
||||||
|
<attribute name="test" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="bin/testFixtures" path="src/testFixtures/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="gradle_scope" value="testFixtures"/>
|
||||||
|
<attribute name="gradle_used_by_scope" value="testFixtures"/>
|
||||||
|
<attribute name="test" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11/"/>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||||
|
<classpathentry kind="output" path="bin/default"/>
|
||||||
|
</classpath>
|
34
.project
Normal file
34
.project
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>provs-core</name>
|
||||||
|
<comment>Project provs-core created by Buildship.</comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||||
|
</natures>
|
||||||
|
<filteredResources>
|
||||||
|
<filter>
|
||||||
|
<id>1628357951205</id>
|
||||||
|
<name></name>
|
||||||
|
<type>30</type>
|
||||||
|
<matcher>
|
||||||
|
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||||
|
<arguments>node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||||
|
</matcher>
|
||||||
|
</filter>
|
||||||
|
</filteredResources>
|
||||||
|
</projectDescription>
|
13
.settings/org.eclipse.buildship.core.prefs
Normal file
13
.settings/org.eclipse.buildship.core.prefs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
arguments=
|
||||||
|
auto.sync=false
|
||||||
|
build.scans.enabled=false
|
||||||
|
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
|
||||||
|
connection.project.dir=
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
gradle.user.home=
|
||||||
|
java.home=/usr/lib/jvm/java-11-openjdk-amd64
|
||||||
|
jvm.arguments=
|
||||||
|
offline.mode=false
|
||||||
|
override.workspace.settings=true
|
||||||
|
show.console.view=true
|
||||||
|
show.executions.view=true
|
53
bin/main/logback.xml
Normal file
53
bin/main/logback.xml
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<timestamp key="byTime" datePattern="yyyyMMdd'T'HHmmss"/>
|
||||||
|
|
||||||
|
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<target>System.err</target>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
<level>WARN</level>
|
||||||
|
</filter>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{35}) - %msg %n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<target>System.out</target>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
<level>DEBUG</level>
|
||||||
|
</filter>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{35}) - %msg %n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>./logs/provs-${byTime}.log</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>./logs/provs-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||||
|
<maxFileSize>10MB</maxFileSize>
|
||||||
|
<maxHistory>3</maxHistory>
|
||||||
|
<totalSizeCap>1GB</totalSizeCap>
|
||||||
|
</rollingPolicy>
|
||||||
|
<append>true</append>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="net.schmizz" level="ERROR">
|
||||||
|
<appender-ref ref="STDERR" />
|
||||||
|
<appender-ref ref="FILE" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="STDERR" />
|
||||||
|
<appender-ref ref="FILE" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<root level="WARN">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
439
bin/main/org/domaindrivenarchitecture/provs/core/Prov.kt
Normal file
439
bin/main/org/domaindrivenarchitecture/provs/core/Prov.kt
Normal file
|
@ -0,0 +1,439 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.platforms.SHELL
|
||||||
|
import org.domaindrivenarchitecture.provs.core.platforms.UbuntuProv
|
||||||
|
import org.domaindrivenarchitecture.provs.core.platforms.WinProv
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.LocalProcessor
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.Processor
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
|
||||||
|
enum class ProgressType { NONE, DOTS, BASIC, FULL_LOG }
|
||||||
|
enum class ResultMode { NONE, LAST, ALL, FAILEXIT }
|
||||||
|
enum class OS { WINDOWS, LINUX }
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This main class offers methods to execute shell commands.
|
||||||
|
* The commands are executed locally, remotely (via ssh) or in a docker container
|
||||||
|
* depending on the processor which is passed to the constructor.
|
||||||
|
*/
|
||||||
|
open class Prov protected constructor(
|
||||||
|
private val processor: Processor,
|
||||||
|
val name: String? = null,
|
||||||
|
private val progressType: ProgressType = ProgressType.BASIC
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
if (progressType == ProgressType.FULL_LOG) {
|
||||||
|
val log = LoggerFactory.getILoggerFactory()
|
||||||
|
.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME) as (ch.qos.logback.classic.Logger)
|
||||||
|
log.level = ch.qos.logback.classic.Level.INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object Factory {
|
||||||
|
|
||||||
|
private lateinit var defaultProvInstance: Prov
|
||||||
|
|
||||||
|
fun defaultInstance(platform: String? = null): Prov {
|
||||||
|
return if (Factory::defaultProvInstance.isInitialized) {
|
||||||
|
defaultProvInstance
|
||||||
|
} else {
|
||||||
|
defaultProvInstance = newInstance(platform = platform, name = "default instance")
|
||||||
|
defaultProvInstance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newInstance(
|
||||||
|
processor: Processor = LocalProcessor(),
|
||||||
|
platform: String? = null,
|
||||||
|
name: String? = null,
|
||||||
|
progressType: ProgressType = ProgressType.BASIC
|
||||||
|
): Prov {
|
||||||
|
|
||||||
|
val os = platform ?: System.getProperty("os.name")
|
||||||
|
|
||||||
|
return when {
|
||||||
|
os.toUpperCase().contains(OS.LINUX.name) -> UbuntuProv(processor, name, progressType)
|
||||||
|
os.toUpperCase().contains(OS.WINDOWS.name) -> WinProv(processor, name, progressType)
|
||||||
|
else -> throw Exception("OS not supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private val internalResults = arrayListOf<ResultLine>()
|
||||||
|
private var level = 0
|
||||||
|
private var previousLevel = 0
|
||||||
|
private var exit = false
|
||||||
|
private var runInContainerWithName: String? = null
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* defines a task with default success behavior, i.e. returns success if all subtasks finished with success.
|
||||||
|
* Same as requireAll.
|
||||||
|
*/
|
||||||
|
fun def(a: Prov.() -> ProvResult): ProvResult {
|
||||||
|
return handle(ResultMode.ALL) { a() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* defines a task, which returns success if the the last subtasks or last value returns success
|
||||||
|
*/
|
||||||
|
fun requireLast(a: Prov.() -> ProvResult): ProvResult {
|
||||||
|
return handle(ResultMode.LAST) { a() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* defines a task, which always returns success
|
||||||
|
*/
|
||||||
|
fun optional(a: Prov.() -> ProvResult): ProvResult {
|
||||||
|
return handle(ResultMode.NONE) { a() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* defines a task, which returns success if all subtasks finished with success
|
||||||
|
*/
|
||||||
|
fun requireAll(a: Prov.() -> ProvResult): ProvResult {
|
||||||
|
return handle(ResultMode.ALL) { a() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* defines a task, which exits the overall execution on failure
|
||||||
|
*/
|
||||||
|
fun exitOnFailure(a: Prov.() -> ProvResult): ProvResult {
|
||||||
|
return handle(ResultMode.FAILEXIT) { a() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: add sudo and update test
|
||||||
|
fun inContainer(containerName: String, a: Prov.() -> ProvResult): ProvResult {
|
||||||
|
runInContainerWithName = containerName
|
||||||
|
val res = handle(ResultMode.ALL) { a() }
|
||||||
|
runInContainerWithName = null
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* execute program with parameters
|
||||||
|
*/
|
||||||
|
fun xec(vararg s: String): ProvResult {
|
||||||
|
val cmd = runInContainerWithName?.let { cmdInContainer(it, *s) } ?: s
|
||||||
|
val result = processor.x(*cmd)
|
||||||
|
return ProvResult(
|
||||||
|
success = (result.exitCode == 0),
|
||||||
|
cmd = result.argsToString(),
|
||||||
|
out = result.out,
|
||||||
|
err = result.err
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* execute program with parameters without logging (to be used if secrets are involved)
|
||||||
|
*/
|
||||||
|
fun xecNoLog(vararg s: String): ProvResult {
|
||||||
|
val cmd = runInContainerWithName?.let { cmdInContainer(it, *s) } ?: s
|
||||||
|
val result = processor.xNoLog(*cmd)
|
||||||
|
return ProvResult(
|
||||||
|
success = (result.exitCode == 0),
|
||||||
|
cmd = "***",
|
||||||
|
out = result.out,
|
||||||
|
err = "***"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a command by using the shell.
|
||||||
|
* Be aware: Executing shell commands that incorporate unsanitized input from an untrusted source
|
||||||
|
* makes a program vulnerable to shell injection, a serious security flaw which can result in arbitrary command execution.
|
||||||
|
* Thus, the use of this method is strongly discouraged in cases where the command string is constructed from external input.
|
||||||
|
*/
|
||||||
|
open fun cmd(cmd: String, dir: String? = null, sudo: Boolean = false): ProvResult {
|
||||||
|
throw Exception("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as method cmd but without logging of the result/output, should be used e.g. if secrets are involved.
|
||||||
|
* Attention: only result is NOT logged the executed command still is.
|
||||||
|
*/
|
||||||
|
open fun cmdNoLog(cmd: String, dir: String? = null, sudo: Boolean = false): ProvResult {
|
||||||
|
throw Exception("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as method cmd but without evaluating the result for the overall success.
|
||||||
|
* Can be used e.g. for checks which might succeed or fail but where failure should not influence overall success
|
||||||
|
*/
|
||||||
|
open fun cmdNoEval(cmd: String, dir: String? = null, sudo: Boolean = false): ProvResult {
|
||||||
|
throw Exception("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes command cmd and returns true in case of success else false.
|
||||||
|
* The success resp. failure is not evaluated, i.e. it is not taken into account for the overall success.
|
||||||
|
*/
|
||||||
|
fun chk(cmd: String, dir: String? = null): Boolean {
|
||||||
|
return cmdNoEval(cmd, dir).success
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a secret by executing the given command.
|
||||||
|
* Returns the result of the command as secret.
|
||||||
|
*/
|
||||||
|
fun getSecret(command: String): Secret? {
|
||||||
|
val result = cmdNoLog(command)
|
||||||
|
return if (result.success && result.out != null) {
|
||||||
|
addResultToEval(ProvResult(true, getCallingMethodName()))
|
||||||
|
Secret(result.out)
|
||||||
|
} else {
|
||||||
|
addResultToEval(ProvResult(false, getCallingMethodName(), err = result.err, exception = result.exception))
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a ProvResult to the overall success evaluation.
|
||||||
|
* Intended for use in methods which do not automatically add results.
|
||||||
|
*/
|
||||||
|
fun addResultToEval(result: ProvResult) = requireAll {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes multiple shell commands. Each command must be in its own line.
|
||||||
|
* Multi-line commands within the script are not supported.
|
||||||
|
* Empty lines and comments (all text behind # in a line) are supported, i.e. they are ignored.
|
||||||
|
*/
|
||||||
|
fun sh(script: String, dir: String? = null, sudo: Boolean = false) = def {
|
||||||
|
val lines = script.trimIndent().replace("\r\n", "\n").split("\n")
|
||||||
|
val linesWithoutComments = lines.stream().map { it.split("#")[0] }
|
||||||
|
val linesNonEmpty = linesWithoutComments.filter { it.trim().isNotEmpty() }
|
||||||
|
|
||||||
|
var success = true
|
||||||
|
|
||||||
|
for (cmd in linesNonEmpty) {
|
||||||
|
if (success) {
|
||||||
|
success = success && cmd(cmd, dir, sudo).success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ProvResult(success)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// todo: put logic in subclasses, such as UbuntuProv
|
||||||
|
private fun cmdInContainer(containerName: String, vararg args: String): Array<String> {
|
||||||
|
return arrayOf(SHELL, "-c", "sudo docker exec $containerName " + buildCommand(*args))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildCommand(vararg args: String): String {
|
||||||
|
return if (args.size == 1)
|
||||||
|
args[0].escapeAndEncloseByDoubleQuoteForShell()
|
||||||
|
else
|
||||||
|
if (args.size == 3 && SHELL.equals(args[0]) && "-c".equals(args[1]))
|
||||||
|
SHELL + " -c " + args[2].escapeAndEncloseByDoubleQuoteForShell()
|
||||||
|
else
|
||||||
|
args.joinToString(separator = " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides result handling, e.g. gather results for result summary
|
||||||
|
*/
|
||||||
|
private fun handle(mode: ResultMode, a: Prov.() -> ProvResult): ProvResult {
|
||||||
|
|
||||||
|
// init
|
||||||
|
if (level == 0) {
|
||||||
|
internalResults.clear()
|
||||||
|
previousLevel = -1
|
||||||
|
exit = false
|
||||||
|
initProgress()
|
||||||
|
}
|
||||||
|
|
||||||
|
// pre-handling
|
||||||
|
val resultIndex = internalResults.size
|
||||||
|
val method = getCallingMethodName()
|
||||||
|
val internalResult = ResultLine(level, method, null)
|
||||||
|
internalResults.add(internalResult)
|
||||||
|
|
||||||
|
previousLevel = level
|
||||||
|
|
||||||
|
level++
|
||||||
|
|
||||||
|
// call the actual function
|
||||||
|
val res = if (!exit) {
|
||||||
|
progress(internalResult)
|
||||||
|
@Suppress("UNUSED_EXPRESSION") // false positive
|
||||||
|
a()
|
||||||
|
} else {
|
||||||
|
ProvResult(false, out = "Exiting due to failure and mode FAILEXIT")
|
||||||
|
}
|
||||||
|
|
||||||
|
level--
|
||||||
|
|
||||||
|
// post-handling
|
||||||
|
val returnValue =
|
||||||
|
if (mode == ResultMode.LAST) {
|
||||||
|
if (internalResultIsLeaf(resultIndex) || method == "cmd")
|
||||||
|
res.copy() else ProvResult(res.success)
|
||||||
|
} else if (mode == ResultMode.ALL) {
|
||||||
|
// leaf
|
||||||
|
if (internalResultIsLeaf(resultIndex)) res.copy()
|
||||||
|
// evaluate subcalls' results
|
||||||
|
else ProvResult(cumulativeSuccessSublevel(resultIndex) ?: false)
|
||||||
|
} else if (mode == ResultMode.NONE) {
|
||||||
|
ProvResult(true)
|
||||||
|
} else if (mode == ResultMode.FAILEXIT) {
|
||||||
|
return if (res.success) {
|
||||||
|
ProvResult(true)
|
||||||
|
} else {
|
||||||
|
exit = true
|
||||||
|
ProvResult(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ProvResult(false, err = "mode unknown")
|
||||||
|
}
|
||||||
|
|
||||||
|
previousLevel = level
|
||||||
|
|
||||||
|
internalResults[resultIndex].provResult = returnValue
|
||||||
|
|
||||||
|
if (level == 0) {
|
||||||
|
endProgress()
|
||||||
|
processor.close()
|
||||||
|
printResults()
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun internalResultIsLeaf(resultIndex: Int): Boolean {
|
||||||
|
return !(resultIndex < internalResults.size - 1 && internalResults[resultIndex + 1].level > internalResults[resultIndex].level)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun cumulativeSuccessSublevel(resultIndex: Int): Boolean? {
|
||||||
|
val currentLevel = internalResults[resultIndex].level
|
||||||
|
var res: Boolean? = null
|
||||||
|
var i = resultIndex + 1
|
||||||
|
while (i < internalResults.size && internalResults[i].level > currentLevel) {
|
||||||
|
if (internalResults[i].level == currentLevel + 1) {
|
||||||
|
res =
|
||||||
|
if (res == null) internalResults[i].provResult?.success else res && (internalResults[i].provResult?.success
|
||||||
|
?: false)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private val ANSI_RESET = "\u001B[0m"
|
||||||
|
private val ANSI_BRIGHT_RED = "\u001B[91m"
|
||||||
|
private val ANSI_BRIGHT_GREEN = "\u001B[92m"
|
||||||
|
// uncomment if needed
|
||||||
|
// val ANSI_BLACK = "\u001B[30m"
|
||||||
|
// val ANSI_RED = "\u001B[31m"
|
||||||
|
// val ANSI_GREEN = "\u001B[32m"
|
||||||
|
// val ANSI_YELLOW = "\u001B[33m"
|
||||||
|
// val ANSI_BLUE = "\u001B[34m"
|
||||||
|
// val ANSI_PURPLE = "\u001B[35m"
|
||||||
|
// val ANSI_CYAN = "\u001B[36m"
|
||||||
|
// val ANSI_WHITE = "\u001B[37m"
|
||||||
|
val ANSI_GRAY = "\u001B[90m"
|
||||||
|
|
||||||
|
private fun printResults() {
|
||||||
|
println(
|
||||||
|
"============================================== SUMMARY " + (if (name != null) "(" + name + ") " else "") +
|
||||||
|
"============================================== "
|
||||||
|
)
|
||||||
|
for (result in internalResults) {
|
||||||
|
println(
|
||||||
|
result.toString().escapeNewline()
|
||||||
|
.replace("Success --", ANSI_BRIGHT_GREEN + "Success" + ANSI_RESET + " --")
|
||||||
|
.replace("FAILED --", ANSI_BRIGHT_RED + "FAILED" + ANSI_RESET + " --")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (internalResults.size > 1) {
|
||||||
|
println("----------------------------------------------------------------------------------------------------- ")
|
||||||
|
println(
|
||||||
|
"Overall " + internalResults[0].toString().take(10)
|
||||||
|
.replace("Success", ANSI_BRIGHT_GREEN + "Success" + ANSI_RESET)
|
||||||
|
.replace("FAILED", ANSI_BRIGHT_RED + "FAILED" + ANSI_RESET)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
println("============================================ SUMMARY END ============================================ " + newline())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.formattedAsResultLine(): String = this
|
||||||
|
.replace("Success", ANSI_BRIGHT_GREEN + "Success" + ANSI_RESET)
|
||||||
|
.replace("FAILED", ANSI_BRIGHT_RED + "FAILED" + ANSI_RESET)
|
||||||
|
.replace("executing...", ANSI_GRAY + "executing..." + ANSI_RESET)
|
||||||
|
|
||||||
|
|
||||||
|
private fun initProgress() {
|
||||||
|
if ((progressType == ProgressType.DOTS) || (progressType == ProgressType.BASIC)) {
|
||||||
|
println("---------- Processing started ----------")
|
||||||
|
System.out.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun progress(line: ResultLine) {
|
||||||
|
if (progressType == ProgressType.DOTS) {
|
||||||
|
print(".")
|
||||||
|
System.out.flush()
|
||||||
|
} else if (progressType == ProgressType.BASIC) {
|
||||||
|
val shortLine = line.inProgress()
|
||||||
|
if (!shortLine.endsWith("cmd") && !shortLine.endsWith("sh")) {
|
||||||
|
println(shortLine.formattedAsResultLine())
|
||||||
|
System.out.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun endProgress() {
|
||||||
|
if ((progressType == ProgressType.DOTS) || (progressType == ProgressType.BASIC)) {
|
||||||
|
println("---------- Processing completed ----------")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal data class ResultLine(val level: Int, val method: String?, var provResult: ProvResult?) {
|
||||||
|
override fun toString(): String {
|
||||||
|
val provResult = provResult
|
||||||
|
return if (provResult != null) {
|
||||||
|
prefix(level) + (if (provResult.success) "Success -- " else "FAILED -- ") +
|
||||||
|
method + " " + (provResult.cmd ?: "") +
|
||||||
|
(if (!provResult.success && provResult.err != null) " -- Error: " + provResult.err.escapeNewline() else "")
|
||||||
|
} else
|
||||||
|
prefix(level) + method + " " + "... in progress ... "
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun inProgress(): String {
|
||||||
|
return prefix(level) + "executing... -- " + method
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prefix(level: Int): String {
|
||||||
|
return "---".repeat(level) + "> "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Prov.myfu() = def {
|
||||||
|
cmd("echo asdf222")
|
||||||
|
}
|
||||||
|
fun main() {
|
||||||
|
|
||||||
|
local().def {
|
||||||
|
cmd("echo asdfasdf")
|
||||||
|
myfu()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core
|
||||||
|
|
||||||
|
|
||||||
|
data class ProvResult(val success: Boolean,
|
||||||
|
val cmd: String? = null,
|
||||||
|
val out: String? = null,
|
||||||
|
val err: String? = null,
|
||||||
|
val exception: Exception? = null,
|
||||||
|
val exit: String? = null) {
|
||||||
|
|
||||||
|
constructor(returnCode : Int) : this(returnCode == 0)
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "ProvResult:: ${if (success) "Succeeded" else "FAILED"} -- ${if (!cmd.isNullOrEmpty()) "Name: " +
|
||||||
|
cmd.escapeNewline() + ", " else ""}${if (!out.isNullOrEmpty()) "Details: $out" else ""}" +
|
||||||
|
(exception?.run { " Exception: " + toString() } ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun toShortString() : String {
|
||||||
|
return "ProvResult:: ${if (success) "Succeeded" else "FAILED"} -- " +
|
||||||
|
if (!success)
|
||||||
|
(if (out != null) "Details: $out " else "" +
|
||||||
|
if (err != null) " Error: " + err else "") else ""
|
||||||
|
}
|
||||||
|
}
|
14
bin/main/org/domaindrivenarchitecture/provs/core/Secret.kt
Normal file
14
bin/main/org/domaindrivenarchitecture/provs/core/Secret.kt
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core
|
||||||
|
|
||||||
|
|
||||||
|
open class Secret(private val value: String) {
|
||||||
|
override fun toString(): String {
|
||||||
|
return "********"
|
||||||
|
}
|
||||||
|
fun plain() : String {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Password(plainPassword: String) : Secret(plainPassword)
|
112
bin/main/org/domaindrivenarchitecture/provs/core/Utils.kt
Normal file
112
bin/main/org/domaindrivenarchitecture/provs/core/Utils.kt
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.provideContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.ContainerStartMode
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.ContainerUbuntuHostProcessor
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.RemoteProcessor
|
||||||
|
import org.domaindrivenarchitecture.provs.core.tags.Api
|
||||||
|
import java.io.File
|
||||||
|
import java.net.InetAddress
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the calling function but excluding some functions of the prov framework
|
||||||
|
* in order to return the "real" calling function.
|
||||||
|
* Note: names of inner functions (i.e. which are defined inside other functions) are not
|
||||||
|
* supported in the sense that always the name of the outer function is returned instead.
|
||||||
|
*/
|
||||||
|
fun getCallingMethodName(): String? {
|
||||||
|
val offsetVal = 1
|
||||||
|
val exclude = arrayOf("def", "record", "invoke", "invoke0", "handle", "def\$default", "addResultToEval")
|
||||||
|
// suffixes are also ignored as method names but will be added as suffix in the evaluation results
|
||||||
|
val suffixes = arrayOf("optional", "requireAll", "requireLast", "inContainer")
|
||||||
|
|
||||||
|
var suffix = ""
|
||||||
|
val callingFrame = Thread.currentThread().stackTrace
|
||||||
|
for (i in 0 until (callingFrame.size - 1)) {
|
||||||
|
if (callingFrame[i].methodName == "getCallingMethodName") {
|
||||||
|
var method = callingFrame[i + offsetVal].methodName
|
||||||
|
var inc = 0
|
||||||
|
while ((method in exclude) or (method in suffixes)) {
|
||||||
|
if (method in suffixes && suffix == "") {
|
||||||
|
suffix = method
|
||||||
|
}
|
||||||
|
inc++
|
||||||
|
method = callingFrame[i + offsetVal + inc].methodName
|
||||||
|
}
|
||||||
|
return method + if (suffix.isBlank()) "" else " ($suffix)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun String.escapeNewline(): String = this.replace("\r\n", "\\n").replace("\n", "\\n")
|
||||||
|
fun String.escapeBackslash(): String = this.replace("\\", "\\\\")
|
||||||
|
fun String.escapeDoubleQuote(): String = this.replace("\"", "\\\"")
|
||||||
|
fun String.escapeSingleQuote(): String = this.replace("'", "\'")
|
||||||
|
fun String.escapeSingleQuoteForShell(): String = this.replace("'", "'\"'\"'")
|
||||||
|
fun String.escapeProcentForPrintf(): String = this.replace("%", "%%")
|
||||||
|
|
||||||
|
// see https://www.shellscript.sh/escape.html
|
||||||
|
fun String.escapeAndEncloseByDoubleQuoteForShell(): String {
|
||||||
|
return "\"" + this.escapeBackslash().replace("`", "\\`").escapeDoubleQuote().replace("$", "\\$") + "\""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hostUserHome(): String = System.getProperty("user.home") + fileSeparator()
|
||||||
|
fun newline(): String = System.getProperty("line.separator")
|
||||||
|
fun fileSeparator(): String = File.separator
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns default local Prov instance.
|
||||||
|
*/
|
||||||
|
@Suppress("unused") // used by other libraries resp. KotlinScript
|
||||||
|
fun local(): Prov {
|
||||||
|
return Prov.defaultInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Prov instance for remote host with remote user with provided password.
|
||||||
|
* If password is null, connection is done by ssh-key.
|
||||||
|
* Platform (Linux, Windows) must be provided if different from local platform.
|
||||||
|
*/
|
||||||
|
@Api // used by other libraries resp. KotlinScript
|
||||||
|
fun remote(host: String, remoteUser: String, password: Secret? = null, platform: String? = null): Prov {
|
||||||
|
require(host.isNotEmpty(), { "Host must not be empty." })
|
||||||
|
require(remoteUser.isNotEmpty(), { "Remote user must not be empty." })
|
||||||
|
|
||||||
|
return Prov.newInstance(RemoteProcessor(InetAddress.getByName(host), remoteUser, password), platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Prov instance running in a local docker container with name containerName.
|
||||||
|
* A potentially existing container with the same name is reused by default resp. if
|
||||||
|
* parameter useExistingContainer is set to true.
|
||||||
|
* If a new container needs to be created, on Linux systems the image _ubuntu_ is used.
|
||||||
|
*/
|
||||||
|
@Api // used by other libraries resp. KotlinScript
|
||||||
|
fun docker(containerName: String = "provs_default", useExistingContainer: Boolean = true): Prov {
|
||||||
|
|
||||||
|
val os = System.getProperty("os.name")
|
||||||
|
|
||||||
|
if ("Linux".equals(os)) {
|
||||||
|
val defaultDockerImage = "ubuntu"
|
||||||
|
|
||||||
|
local().provideContainer(containerName, defaultDockerImage)
|
||||||
|
|
||||||
|
return Prov.newInstance(
|
||||||
|
ContainerUbuntuHostProcessor(
|
||||||
|
containerName,
|
||||||
|
defaultDockerImage,
|
||||||
|
if (useExistingContainer)
|
||||||
|
ContainerStartMode.USE_RUNNING_ELSE_CREATE
|
||||||
|
else
|
||||||
|
ContainerStartMode.CREATE_NEW_KILL_EXISTING
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
throw RuntimeException("ERROR: method docker() is currently not supported for " + os)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.docker
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.dockerimages.DockerImage
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.platforms.*
|
||||||
|
import org.domaindrivenarchitecture.provs.core.platforms.UbuntuProv
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.ContainerStartMode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a docker image if not yet existing.
|
||||||
|
*/
|
||||||
|
fun Prov.dockerProvideImage(image: DockerImage, skipIfExisting: Boolean = true, sudo: Boolean = true) : ProvResult {
|
||||||
|
if (this is UbuntuProv) {
|
||||||
|
return this.dockerProvideImagePlatform(image, skipIfExisting, sudo)
|
||||||
|
} else {
|
||||||
|
throw RuntimeException("docker not yet supported for " + (this as UbuntuProv).javaClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the specified docker image exists.
|
||||||
|
*/
|
||||||
|
fun Prov.dockerImageExists(imageName: String, sudo: Boolean = true) : Boolean {
|
||||||
|
if (this is UbuntuProv) {
|
||||||
|
return this.dockerImageExistsPlatform(imageName, sudo)
|
||||||
|
} else {
|
||||||
|
throw RuntimeException("docker not yet supported for " + (this as UbuntuProv).javaClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and runs a new container with name _containerName_ for image _imageName_ if not yet existing.
|
||||||
|
* In case the container already exists, the parameter _startMode_ determines
|
||||||
|
* if the running container is just kept (default behavior)
|
||||||
|
* or if the running container is stopped and removed and a new container is created
|
||||||
|
* or if the method results in a failure result.
|
||||||
|
*/
|
||||||
|
fun Prov.provideContainer(
|
||||||
|
containerName: String,
|
||||||
|
imageName: String = "ubuntu",
|
||||||
|
startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
||||||
|
sudo: Boolean = true
|
||||||
|
) : ProvResult {
|
||||||
|
if (this is UbuntuProv) {
|
||||||
|
return this.provideContainerPlatform(containerName, imageName, startMode, sudo)
|
||||||
|
} else {
|
||||||
|
throw RuntimeException("docker not yet supported for " + (this as UbuntuProv).javaClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.containerRuns(containerName: String, sudo: Boolean = true) : Boolean {
|
||||||
|
if (this is UbuntuProv) {
|
||||||
|
return this.containerRunsPlatform(containerName, sudo)
|
||||||
|
} else {
|
||||||
|
throw RuntimeException("docker not yet supported for " + (this as UbuntuProv).javaClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.runContainer(
|
||||||
|
containerName: String = "provs_default",
|
||||||
|
imageName: String = "ubuntu",
|
||||||
|
sudo: Boolean = true
|
||||||
|
) : ProvResult {
|
||||||
|
if (this is UbuntuProv) {
|
||||||
|
return this.runContainerPlatform(containerName, imageName, sudo)
|
||||||
|
} else {
|
||||||
|
throw RuntimeException("docker not yet supported for " + (this as UbuntuProv).javaClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.exitAndRmContainer(
|
||||||
|
containerName: String,
|
||||||
|
sudo: Boolean = true
|
||||||
|
) : ProvResult {
|
||||||
|
if (this is UbuntuProv) {
|
||||||
|
return this.exitAndRmContainerPlatform(containerName, sudo)
|
||||||
|
} else {
|
||||||
|
throw RuntimeException("docker not yet supported for " + (this as UbuntuProv).javaClass)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.docker.dockerimages
|
||||||
|
|
||||||
|
|
||||||
|
interface DockerImage {
|
||||||
|
fun imageName() : String
|
||||||
|
fun imageText() : String
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a docker image based on ubuntu additionally with a non-root default user and sudo isntalled
|
||||||
|
*/
|
||||||
|
class UbuntuPlusUser(private val userName: String = "testuser") : DockerImage {
|
||||||
|
|
||||||
|
override fun imageName(): String {
|
||||||
|
return "ubuntu_plus_user"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageText(): String {
|
||||||
|
return """
|
||||||
|
FROM ubuntu:18.04
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get -y install sudo
|
||||||
|
RUN useradd -m $userName && echo "$userName:$userName" | chpasswd && adduser $userName sudo
|
||||||
|
RUN echo "$userName ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/$userName
|
||||||
|
|
||||||
|
USER $userName
|
||||||
|
CMD /bin/bash
|
||||||
|
WORKDIR /home/$userName
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.docker.platforms
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.containerRuns
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.dockerImageExists
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.exitAndRmContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.dockerimages.DockerImage
|
||||||
|
import org.domaindrivenarchitecture.provs.core.escapeSingleQuote
|
||||||
|
import org.domaindrivenarchitecture.provs.core.fileSeparator
|
||||||
|
import org.domaindrivenarchitecture.provs.core.hostUserHome
|
||||||
|
import org.domaindrivenarchitecture.provs.core.platforms.UbuntuProv
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.ContainerStartMode
|
||||||
|
|
||||||
|
|
||||||
|
fun UbuntuProv.provideContainerPlatform(
|
||||||
|
containerName: String,
|
||||||
|
imageName: String = "ubuntu",
|
||||||
|
startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
||||||
|
sudo: Boolean = true
|
||||||
|
): ProvResult = requireLast {
|
||||||
|
val dockerCmd = if (sudo) "sudo docker " else "docker "
|
||||||
|
|
||||||
|
if (startMode == ContainerStartMode.CREATE_NEW_KILL_EXISTING) {
|
||||||
|
exitAndRmContainer(containerName)
|
||||||
|
}
|
||||||
|
if ((startMode == ContainerStartMode.CREATE_NEW_KILL_EXISTING) || (startMode == ContainerStartMode.CREATE_NEW_FAIL_IF_EXISTING)) {
|
||||||
|
if (!cmd(dockerCmd + "run -dit --name=$containerName $imageName").success) {
|
||||||
|
throw RuntimeException("could not start docker")
|
||||||
|
}
|
||||||
|
} else if (startMode == ContainerStartMode.USE_RUNNING_ELSE_CREATE) {
|
||||||
|
val runCheckResult = cmdNoEval(dockerCmd + "inspect -f '{{.State.Running}}' $containerName")
|
||||||
|
|
||||||
|
// if either container not found or container found but not running
|
||||||
|
if (!runCheckResult.success || "false\n" == runCheckResult.out) {
|
||||||
|
cmdNoEval(dockerCmd + "rm -f $containerName")
|
||||||
|
cmd(dockerCmd + "run -dit --name=$containerName $imageName")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ProvResult(containerRuns(containerName, sudo))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun UbuntuProv.containerRunsPlatform(containerName: String, sudo: Boolean = true): Boolean {
|
||||||
|
val dockerCmd = if (sudo) "sudo docker " else "docker "
|
||||||
|
return cmdNoEval(dockerCmd + "inspect -f '{{.State.Running}}' $containerName").out?.equals("true\n") ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun UbuntuProv.runContainerPlatform(
|
||||||
|
containerName: String = "defaultProvContainer",
|
||||||
|
imageName: String = "ubuntu",
|
||||||
|
sudo: Boolean = true
|
||||||
|
) = def {
|
||||||
|
val dockerCmd = if (sudo) "sudo docker " else "docker "
|
||||||
|
cmd(dockerCmd + "run -dit --name=$containerName $imageName")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun UbuntuProv.containerExecPlatform(containerName: String, cmd: String, sudo: Boolean = true) = def {
|
||||||
|
val dockerCmd = if (sudo) "sudo docker " else "docker "
|
||||||
|
cmd(dockerCmd + "exec $containerName $cmd")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun UbuntuProv.dockerProvideImagePlatform(image: DockerImage, skipIfExisting: Boolean, sudo: Boolean): ProvResult {
|
||||||
|
val dockerCmd = if (sudo) "sudo docker " else "docker "
|
||||||
|
|
||||||
|
if (skipIfExisting && dockerImageExists(image.imageName())) {
|
||||||
|
return ProvResult(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
val path = hostUserHome() + "tmp_docker_img" + fileSeparator()
|
||||||
|
|
||||||
|
if (!xec("test", "-d", path).success) {
|
||||||
|
cmd("cd ${hostUserHome()} && mkdir tmp_docker_img")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd("cd $path && printf '${image.imageText().escapeSingleQuote()}' > Dockerfile")
|
||||||
|
|
||||||
|
return cmd("cd $path && "+dockerCmd+"build --tag ${image.imageName()} .")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun UbuntuProv.dockerImageExistsPlatform(imageName: String, sudo: Boolean): Boolean {
|
||||||
|
val dockerCmd = if (sudo) "sudo docker " else "docker "
|
||||||
|
|
||||||
|
return (cmdNoEval(dockerCmd + "images $imageName -q").out != "")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun UbuntuProv.exitAndRmContainerPlatform(
|
||||||
|
containerName: String,
|
||||||
|
sudo: Boolean
|
||||||
|
) = requireAll {
|
||||||
|
val dockerCmd = if (sudo) "sudo docker " else "docker "
|
||||||
|
|
||||||
|
if (containerRuns(containerName)) {
|
||||||
|
cmd(dockerCmd + "stop $containerName")
|
||||||
|
}
|
||||||
|
cmd(dockerCmd + "rm $containerName")
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.entry
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls a static method of a class.
|
||||||
|
* Only methods are supported with either no parameters or with one vararg parameter of type String.
|
||||||
|
* Methods with a vararg parameter must be called with at least one argument.
|
||||||
|
*
|
||||||
|
* @param args specify class and (optionally) method and parameters, in detail:
|
||||||
|
* @param args[0] fully-qualified class name of the class to be called
|
||||||
|
* @param args[1] (optional) static method of the class with a vararg parameter of type String; if not specified, the "main" method is used
|
||||||
|
* @param args[2...] (optional) String parameters that are passed to the method; can be only used if method name (args[1]) is provided
|
||||||
|
*/
|
||||||
|
fun main(vararg args: String) {
|
||||||
|
|
||||||
|
if (args.isNotEmpty()) {
|
||||||
|
val className = args[0]
|
||||||
|
|
||||||
|
val jClass = Class.forName(className)
|
||||||
|
|
||||||
|
val parameterTypeStringArray = arrayOf<Class<*>>(
|
||||||
|
Array<String>::class.java
|
||||||
|
)
|
||||||
|
val method = if (args.size == 1) {
|
||||||
|
jClass.getMethod("main", *parameterTypeStringArray)
|
||||||
|
} else {
|
||||||
|
jClass.getMethod(args[1], *parameterTypeStringArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.size <= 2) {
|
||||||
|
method.invoke(null, emptyArray<String>())
|
||||||
|
} else {
|
||||||
|
method.invoke(null, args.drop(2).toTypedArray())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println("Usage: <packageName.className> <optional static methodName> <optional parameters ... >")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.platforms
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProgressType
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.core.escapeAndEncloseByDoubleQuoteForShell
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.LocalProcessor
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.Processor
|
||||||
|
|
||||||
|
const val SHELL = "/bin/bash"
|
||||||
|
|
||||||
|
|
||||||
|
class UbuntuProv internal constructor(processor : Processor = LocalProcessor(), name: String? = null, progressType: ProgressType)
|
||||||
|
: Prov(processor, name, progressType) {
|
||||||
|
|
||||||
|
override fun cmd(cmd: String, dir: String?, sudo: Boolean) : ProvResult = def {
|
||||||
|
xec(SHELL, "-c", commandWithDirAndSudo(cmd, dir, sudo))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cmdNoLog(cmd: String, dir: String?, sudo: Boolean) : ProvResult {
|
||||||
|
return xecNoLog(SHELL, "-c", commandWithDirAndSudo(cmd, dir, sudo))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cmdNoEval(cmd: String, dir: String?, sudo: Boolean) : ProvResult {
|
||||||
|
return xec(SHELL, "-c", commandWithDirAndSudo(cmd, dir, sudo))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun commandWithDirAndSudo(cmd: String, dir: String?, sudo: Boolean): String {
|
||||||
|
val cmdWithDir= if (dir == null) cmd else "cd $dir && $cmd"
|
||||||
|
return if (sudo) cmdWithDir.sudoize() else cmdWithDir
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.sudoize(): String {
|
||||||
|
return "sudo " + SHELL + " -c " + this.escapeAndEncloseByDoubleQuoteForShell()
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.platforms
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProgressType
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.LocalProcessor
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.Processor
|
||||||
|
|
||||||
|
|
||||||
|
class WinProv internal constructor(processor : Processor = LocalProcessor(), name: String? = null, progressType: ProgressType)
|
||||||
|
: Prov(processor, name, progressType) {
|
||||||
|
|
||||||
|
private val SHELL = "cmd.exe"
|
||||||
|
|
||||||
|
override fun cmd(cmd: String, dir: String?, sudo: Boolean) : ProvResult = def {
|
||||||
|
require(!sudo, {"sudo not supported"})
|
||||||
|
xec(SHELL, "/c", if (dir == null) cmd else "cd $dir && $cmd")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cmdNoLog(cmd: String, dir: String?, sudo: Boolean) : ProvResult = def {
|
||||||
|
require(!sudo, {"sudo not supported"})
|
||||||
|
xecNoLog(SHELL, "/c", if (dir == null) cmd else "cd $dir && $cmd")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun cmdNoEval(cmd: String, dir: String?, sudo: Boolean) : ProvResult {
|
||||||
|
require(!sudo, {"sudo not supported"})
|
||||||
|
return xec(SHELL, "/c", if (dir == null) cmd else "cd $dir && $cmd")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.processors
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProgressType
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.provideContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.core.escapeAndEncloseByDoubleQuoteForShell
|
||||||
|
import org.domaindrivenarchitecture.provs.core.platforms.SHELL
|
||||||
|
import org.domaindrivenarchitecture.provs.core.tags.Api
|
||||||
|
|
||||||
|
enum class ContainerStartMode {
|
||||||
|
USE_RUNNING_ELSE_CREATE,
|
||||||
|
CREATE_NEW_KILL_EXISTING,
|
||||||
|
CREATE_NEW_FAIL_IF_EXISTING
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ContainerEndMode {
|
||||||
|
EXIT_AND_REMOVE,
|
||||||
|
KEEP_RUNNING
|
||||||
|
}
|
||||||
|
|
||||||
|
open class ContainerUbuntuHostProcessor(
|
||||||
|
private val containerName: String = "default_provs_container",
|
||||||
|
@Api // suppress false positive warning
|
||||||
|
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,
|
||||||
|
@Api // suppress false positive warning
|
||||||
|
private val sudo: Boolean = true
|
||||||
|
) : Processor {
|
||||||
|
private val dockerCmd = if (sudo) "sudo docker " else "docker "
|
||||||
|
private var localExecution = LocalProcessor()
|
||||||
|
private var a = Prov.newInstance(name = "LocalProcessor for Docker operations", progressType = ProgressType.NONE)
|
||||||
|
|
||||||
|
init {
|
||||||
|
val r = a.provideContainer(containerName, dockerImage, startMode, sudo)
|
||||||
|
if (!r.success)
|
||||||
|
throw RuntimeException("Could not start docker image: " + r.toString(), r.exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun x(vararg args: String): ProcessResult {
|
||||||
|
return localExecution.x("sh", "-c", dockerCmd + "exec $containerName " + buildCommand(*args))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun xNoLog(vararg args: String): ProcessResult {
|
||||||
|
return localExecution.xNoLog("sh", "-c", dockerCmd + "exec $containerName " + buildCommand(*args))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exitAndRm() {
|
||||||
|
localExecution.x(SHELL, "-c", dockerCmd + "stop $containerName")
|
||||||
|
localExecution.x(SHELL, "-c", dockerCmd + "rm $containerName")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun quoteString(s: String): String {
|
||||||
|
return s.escapeAndEncloseByDoubleQuoteForShell()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildCommand(vararg args: String) : String {
|
||||||
|
return if (args.size == 1) quoteString(args[0]) else
|
||||||
|
if (args.size == 3 && SHELL == args[0] && "-c" == args[1]) SHELL + " -c " + quoteString(args[2])
|
||||||
|
else args.joinToString(separator = " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun finalize() {
|
||||||
|
if (endMode == ContainerEndMode.EXIT_AND_REMOVE) {
|
||||||
|
exitAndRm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.processors
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.escapeNewline
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
|
|
||||||
|
private fun getOsName(): String {
|
||||||
|
return System.getProperty("os.name")
|
||||||
|
}
|
||||||
|
|
||||||
|
open class LocalProcessor : Processor {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Suppress("JAVA_CLASS_ON_COMPANION")
|
||||||
|
private val log = LoggerFactory.getLogger(javaClass.enclosingClass)
|
||||||
|
|
||||||
|
var charset: Charset = if (getOsName().contains("Windows")) Charset.forName("Windows-1252") else Charset.defaultCharset()
|
||||||
|
init {
|
||||||
|
log.info("os.name: " + getOsName())
|
||||||
|
log.info("user.home: " + System.getProperty("user.home"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun workingDir() : String
|
||||||
|
{
|
||||||
|
return System.getProperty("user.home") ?: File.separator
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun x(vararg args: String): ProcessResult {
|
||||||
|
return execute(true, *args)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun xNoLog(vararg args: String): ProcessResult {
|
||||||
|
return execute(false, *args)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun execute(logging: Boolean, vararg args: String): ProcessResult {
|
||||||
|
try {
|
||||||
|
var prefix = "******************** Prov: "
|
||||||
|
if (logging) {
|
||||||
|
for (arg in args) {
|
||||||
|
prefix += " \"${arg.escapeNewline()}\""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prefix += "\"xxxxxxxx\""
|
||||||
|
}
|
||||||
|
log.info(prefix)
|
||||||
|
|
||||||
|
val proc = ProcessBuilder(args.toList())
|
||||||
|
.directory(File(workingDir()))
|
||||||
|
.redirectOutput(ProcessBuilder.Redirect.PIPE)
|
||||||
|
.redirectError(ProcessBuilder.Redirect.PIPE)
|
||||||
|
.start()
|
||||||
|
|
||||||
|
val c = proc.waitFor()
|
||||||
|
|
||||||
|
val r = ProcessResult(
|
||||||
|
c,
|
||||||
|
proc.inputStream.bufferedReader(charset).readText(),
|
||||||
|
proc.errorStream.bufferedReader(charset).readText(),
|
||||||
|
args = args
|
||||||
|
)
|
||||||
|
if (logging) {
|
||||||
|
log.info(r.toString())
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
return ProcessResult(-1, ex = e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.processors
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("unused") // used externally
|
||||||
|
class PrintOnlyProcessor : Processor {
|
||||||
|
|
||||||
|
override fun x(vararg args: String): ProcessResult
|
||||||
|
{
|
||||||
|
print("PrintOnlyProcessor >>> ")
|
||||||
|
for (n in args) print("\"$n\" ")
|
||||||
|
println()
|
||||||
|
return ProcessResult(0, args = args)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun xNoLog(vararg args: String): ProcessResult
|
||||||
|
{
|
||||||
|
print("PrintOnlyProcessor >>> ********")
|
||||||
|
return ProcessResult(0, args = args)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.processors
|
||||||
|
|
||||||
|
|
||||||
|
interface Processor {
|
||||||
|
fun x(vararg args: String): ProcessResult
|
||||||
|
fun xNoLog(vararg args: String): ProcessResult
|
||||||
|
fun close() {
|
||||||
|
// no action needed for most processors; if action is needed when closing, this method must be overwritten in the subclass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data class ProcessResult(val exitCode: Int, val out: String? = null, val err: String? = null, val ex: Exception? = null, val args: Array<out String> = emptyArray()) {
|
||||||
|
|
||||||
|
private fun success(): Boolean {
|
||||||
|
return (exitCode == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun argsToString() : String {
|
||||||
|
return args.joinToString(
|
||||||
|
separator = ", ",
|
||||||
|
prefix = "[",
|
||||||
|
postfix = "]",
|
||||||
|
limit = 4,
|
||||||
|
truncated = " ..."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "--->>> ProcessResult: ${if (success()) "Succeeded" else "FAILED"} -- Code: $exitCode, ${if (!out.isNullOrEmpty()) "Out: $out, " else ""}${if (!err.isNullOrEmpty()) "Err: $err" else ""}" + argsToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as ProcessResult
|
||||||
|
|
||||||
|
if (exitCode != other.exitCode) return false
|
||||||
|
if (out != other.out) return false
|
||||||
|
if (err != other.err) return false
|
||||||
|
if (ex != other.ex) return false
|
||||||
|
if (!args.contentEquals(other.args)) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = exitCode
|
||||||
|
result = 31 * result + (out?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (err?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + ex.hashCode()
|
||||||
|
result = 31 * result + args.contentHashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.processors
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.core.escapeAndEncloseByDoubleQuoteForShell
|
||||||
|
import org.domaindrivenarchitecture.provs.core.escapeNewline
|
||||||
|
import org.domaindrivenarchitecture.provs.core.platforms.SHELL
|
||||||
|
import net.schmizz.sshj.SSHClient
|
||||||
|
import net.schmizz.sshj.connection.channel.direct.Session
|
||||||
|
import net.schmizz.sshj.connection.channel.direct.Session.Command
|
||||||
|
import net.schmizz.sshj.transport.verification.PromiscuousVerifier
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
|
class RemoteProcessor(ip: InetAddress, user: String, password: Secret? = null) : Processor {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Suppress("JAVA_CLASS_ON_COMPANION")
|
||||||
|
private val log = LoggerFactory.getLogger(javaClass.enclosingClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val ssh = SSHClient()
|
||||||
|
|
||||||
|
init {
|
||||||
|
try {
|
||||||
|
log.info("Connecting to $ip with user: $user with " + if (password != null) "password" else "ssh-key")
|
||||||
|
|
||||||
|
ssh.loadKnownHosts()
|
||||||
|
|
||||||
|
// todo: replace PromiscuousVerifier by more secure solution
|
||||||
|
ssh.addHostKeyVerifier(PromiscuousVerifier())
|
||||||
|
ssh.connect(ip)
|
||||||
|
|
||||||
|
if (password != null) {
|
||||||
|
ssh.authPassword(user, password.plain())
|
||||||
|
} else {
|
||||||
|
val base = System.getProperty("user.home") + File.separator + ".ssh" + File.separator
|
||||||
|
ssh.authPublickey(user, base + "id_rsa", base + "id_dsa", base + "id_ed25519", base + "id_ecdsa")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
try {
|
||||||
|
ssh.disconnect()
|
||||||
|
} finally {
|
||||||
|
log.error("Got exception when initializing ssh: " + e.message)
|
||||||
|
throw RuntimeException("Error when initializing ssh", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun x(vararg args: String): ProcessResult {
|
||||||
|
return execute(true, *args)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun xNoLog(vararg args: String): ProcessResult {
|
||||||
|
return execute(false, *args)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun execute(logging: Boolean, vararg args: String): ProcessResult {
|
||||||
|
var prefix = "******************** Prov: "
|
||||||
|
if (logging) {
|
||||||
|
for (arg in args) {
|
||||||
|
prefix += " \"${arg.escapeNewline()}\""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prefix += "\"xxxxxxxx\""
|
||||||
|
}
|
||||||
|
log.info(prefix)
|
||||||
|
|
||||||
|
val cmdString: String =
|
||||||
|
if (args.size == 1)
|
||||||
|
args[0].escapeAndEncloseByDoubleQuoteForShell()
|
||||||
|
else
|
||||||
|
if (args.size == 3 && SHELL == args[0] && "-c" == args[1])
|
||||||
|
SHELL + " -c " + args[2].escapeAndEncloseByDoubleQuoteForShell()
|
||||||
|
else
|
||||||
|
args.joinToString(separator = " ")
|
||||||
|
|
||||||
|
var session: Session? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
session = ssh.startSession()
|
||||||
|
|
||||||
|
val cmd: Command = session!!.exec(cmdString)
|
||||||
|
val out = BufferedReader(InputStreamReader(cmd.inputStream)).use { it.readText() }
|
||||||
|
val err = BufferedReader(InputStreamReader(cmd.errorStream)).use { it.readText() }
|
||||||
|
cmd.join(100, TimeUnit.SECONDS)
|
||||||
|
|
||||||
|
val cmdRes = ProcessResult(cmd.exitStatus, out, err, args = args)
|
||||||
|
if (logging) {
|
||||||
|
log.info(cmdRes.toString())
|
||||||
|
}
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
return cmdRes
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
try {
|
||||||
|
session?.close()
|
||||||
|
} finally {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
return ProcessResult(
|
||||||
|
-1,
|
||||||
|
err = "Error when opening or executing remote ssh session (Pls check host, user, password resp. ssh key) - ",
|
||||||
|
ex = e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
try {
|
||||||
|
log.info("Disconnecting ssh.")
|
||||||
|
ssh.disconnect()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
// No prov required
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun finalize() {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.tags
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
|
||||||
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
|
@Suppress("unused") // element is offered to be used externally
|
||||||
|
annotation class Api
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.demos
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.local
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.helloWorld() = def {
|
||||||
|
cmd("echo Hello world!")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
local().helloWorld()
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.demos
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints some information and settings of the operating system and environment.
|
||||||
|
*
|
||||||
|
* For running locally no arguments are required.
|
||||||
|
* For running remotely either 2 or 3 arguments must be provided:
|
||||||
|
* either host and user for connection by ssh key ()
|
||||||
|
* or host, user and password for password-authenticated connection.
|
||||||
|
* E.g. 172.0.0.123 username or 172.0.0.123 username password
|
||||||
|
*/
|
||||||
|
fun main(vararg args: String) {
|
||||||
|
if (args.isEmpty()) {
|
||||||
|
local().printInfos()
|
||||||
|
} else {
|
||||||
|
if (args.size !in 2..3) {
|
||||||
|
println("Wrong number of arguments. Please specify either host and user if connection is done by ssh key or otherwise host, user and password. E.g. 172.0.0.123 username password")
|
||||||
|
} else {
|
||||||
|
val password = if (args.size == 2) null else Secret(args[3])
|
||||||
|
remote(args[0], args[1], password = password).printInfos()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.printInfos() = def {
|
||||||
|
println("\nUbuntu Version:\n${ubuntuVersion()}")
|
||||||
|
println("\nCurrent directory:\n${currentDir()}")
|
||||||
|
println("\nTime zone:\n${timeZone()}")
|
||||||
|
|
||||||
|
val dir = cmd("pwd").out
|
||||||
|
println("\nCurrent directory: $dir")
|
||||||
|
|
||||||
|
ProvResult(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.ubuntuVersion(): String? {
|
||||||
|
return cmd("lsb_release -a").out
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.currentDir(): String? {
|
||||||
|
return cmd("pwd").out
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.timeZone(): String? {
|
||||||
|
return cmd("cat /etc/timezone").out
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_compounds.monitoring
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base.NginxConf
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base.nginxHttpConf
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.provisionNginxStandAlone
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.prometheus.base.configurePrometheusDocker
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.prometheus.base.runPrometheusDocker
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("unused") // used externally
|
||||||
|
fun Prov.provisionMonitoring() = requireAll {
|
||||||
|
configurePrometheusDocker()
|
||||||
|
runPrometheusDocker()
|
||||||
|
provisionNginxStandAlone(NginxConf.nginxHttpConf())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_compounds.monitoring
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base.NginxConf
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base.nginxAddLocation
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base.nginxCreateSelfSignedCertificate
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base.nginxHttpsConfWithLocationFiles
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.provisionNginxStandAlone
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.prometheus.base.prometheusNginxConfig
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.prometheus.provisionPrometheusDocker
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("unused") // used externally
|
||||||
|
fun Prov.provisionNginxMonitoring(nginxHost: String = "localhost") = def {
|
||||||
|
provisionPrometheusDocker(nginxHost)
|
||||||
|
nginxCreateSelfSignedCertificate()
|
||||||
|
provisionNginxStandAlone(NginxConf.nginxHttpsConfWithLocationFiles())
|
||||||
|
nginxAddLocation("443", nginxHost, "/prometheus", prometheusNginxConfig)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.certbot
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.fileExists
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provisions a certbot for the specified serverName and email to obtain and renew letsencrypt certificates
|
||||||
|
* Parameter can be used to specify certbot options e.g. "--nginx" to configure nginx, see https://certbot.eff.org/docs/using.html#certbot-command-line-options
|
||||||
|
*/
|
||||||
|
fun Prov.provisionCertbot(serverName: String, email: String?, additionalOptions: String? = "") = requireAll {
|
||||||
|
aptInstall("snapd")
|
||||||
|
sh("""
|
||||||
|
sudo snap install core; sudo snap refresh core
|
||||||
|
sudo snap install --classic certbot
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
if (!fileExists("/usr/bin/certbot")) {
|
||||||
|
cmd("sudo ln -s /snap/bin/certbot /usr/bin/certbot")
|
||||||
|
val emailOption = email?.let { " -m $it" } ?: "--register-unsafely-without-email"
|
||||||
|
cmd("sudo certbot $additionalOptions -n --agree-tos $emailOption -d $serverName")
|
||||||
|
} else {
|
||||||
|
ProvResult(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.firewall
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.saveIpTables() = requireAll {
|
||||||
|
sh("""
|
||||||
|
iptables-save > /etc/iptables/rules.v4
|
||||||
|
ip6tables-save > /etc/iptables/rules.v6
|
||||||
|
|
||||||
|
netfilter-persistent save""",
|
||||||
|
sudo = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.makeIpTablesPersistent() = requireAll {
|
||||||
|
// inspired by https://gist.github.com/alonisser/a2c19f5362c2091ac1e7
|
||||||
|
// enables iptables-persistent to be installed without manual input
|
||||||
|
sh("""
|
||||||
|
echo iptables-persistent iptables-persistent/autosave_v4 boolean true | sudo debconf-set-selections
|
||||||
|
echo iptables-persistent iptables-persistent/autosave_v6 boolean true | sudo debconf-set-selections
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
aptInstall("iptables-persistent netfilter-persistent")
|
||||||
|
saveIpTables()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.resetFirewall() = requireAll {
|
||||||
|
sh("""
|
||||||
|
#!/bin/bash
|
||||||
|
sudo iptables -F
|
||||||
|
sudo iptables -X
|
||||||
|
sudo iptables -t nat -F
|
||||||
|
sudo iptables -t nat -X
|
||||||
|
sudo iptables -t mangle -F
|
||||||
|
sudo iptables -t mangle -X
|
||||||
|
|
||||||
|
# the rules allow us to reconnect by opening up all traffic.
|
||||||
|
sudo iptables -P INPUT ACCEPT
|
||||||
|
sudo iptables -P FORWARD ACCEPT
|
||||||
|
sudo iptables -P OUTPUT ACCEPT
|
||||||
|
|
||||||
|
# print out all rules to the console after running this file.
|
||||||
|
sudo iptables -nL
|
||||||
|
""", sudo = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.provisionFirewall(addNetworkProtections: Boolean = false) = requireAll {
|
||||||
|
if (addNetworkProtections) {
|
||||||
|
networkProtections()
|
||||||
|
}
|
||||||
|
|
||||||
|
// inspired by: https://github.com/ChrisTitusTech/firewallsetup/blob/master/firewall
|
||||||
|
sh("""
|
||||||
|
# Firewall
|
||||||
|
|
||||||
|
# Accept all traffic first to avoid ssh lockdown via iptables firewall rules #
|
||||||
|
iptables -P INPUT ACCEPT
|
||||||
|
iptables -P FORWARD ACCEPT
|
||||||
|
iptables -P OUTPUT ACCEPT
|
||||||
|
|
||||||
|
# Flush all chains
|
||||||
|
iptables --flush
|
||||||
|
|
||||||
|
# Allow unlimited traffic on the loopback interface
|
||||||
|
iptables -A INPUT -i lo -j ACCEPT
|
||||||
|
iptables -A OUTPUT -o lo -j ACCEPT
|
||||||
|
|
||||||
|
# Previously initiated and accepted exchanges bypass rule checking
|
||||||
|
# Allow unlimited outbound traffic
|
||||||
|
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||||
|
iptables -A OUTPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
|
||||||
|
|
||||||
|
#Ratelimit SSH for attack protection
|
||||||
|
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
|
||||||
|
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set
|
||||||
|
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT
|
||||||
|
|
||||||
|
# Allow http/https ports to be accessible from the outside
|
||||||
|
iptables -A INPUT -p tcp --dport 80 -m state --state NEW -j ACCEPT # http
|
||||||
|
iptables -A INPUT -p tcp --dport 443 -m state --state NEW -j ACCEPT # https
|
||||||
|
|
||||||
|
# UDP packet rule. This is just a random udp packet rule as an example only
|
||||||
|
# iptables -A INPUT -p udp --dport 5021 -m state --state NEW -j ACCEPT
|
||||||
|
|
||||||
|
# Allow pinging of your server
|
||||||
|
iptables -A INPUT -p icmp --icmp-type 8 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
|
||||||
|
|
||||||
|
|
||||||
|
# Drop all other traffic
|
||||||
|
iptables -A INPUT -j DROP
|
||||||
|
|
||||||
|
# print the activated rules to the console when script is completed
|
||||||
|
iptables -nL
|
||||||
|
|
||||||
|
# Set default policies
|
||||||
|
iptables --policy INPUT DROP
|
||||||
|
iptables --policy OUTPUT DROP
|
||||||
|
iptables --policy FORWARD DROP
|
||||||
|
""", sudo = true)
|
||||||
|
if (chk("docker -v")) {
|
||||||
|
ipTablesRecreateDockerRules()
|
||||||
|
} else {
|
||||||
|
ProvResult(true, "No need to create iptables docker rules as no docker installed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.networkProtections() = def {
|
||||||
|
sh("""
|
||||||
|
# Drop ICMP echo-request messages sent to broadcast or multicast addresses
|
||||||
|
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts
|
||||||
|
|
||||||
|
# Drop source routed packets
|
||||||
|
echo 0 > /proc/sys/net/ipv4/conf/all/accept_source_route
|
||||||
|
|
||||||
|
# Enable TCP SYN cookie protection from SYN floods
|
||||||
|
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
|
||||||
|
|
||||||
|
# Don't accept ICMP redirect messages
|
||||||
|
echo 0 > /proc/sys/net/ipv4/conf/all/accept_redirects
|
||||||
|
|
||||||
|
# Don't send ICMP redirect messages
|
||||||
|
echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects
|
||||||
|
|
||||||
|
# Enable source address spoofing protection
|
||||||
|
echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter
|
||||||
|
|
||||||
|
# Log packets with impossible source addresses
|
||||||
|
echo 1 > /proc/sys/net/ipv4/conf/all/log_martians
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.ipTablesRecreateDockerRules() = requireAll {
|
||||||
|
// see https://stackoverflow.com/questions/25917941/docker-how-to-re-create-dockers-additional-iptables-rules
|
||||||
|
cmd("sudo service docker restart")
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.firewall.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.saveIpTablesToFile() = def {
|
||||||
|
val dateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("-yyyy-MM-dd--HH:mm:ss"))
|
||||||
|
val file = "savedrules$dateTime.txt"
|
||||||
|
sh("""
|
||||||
|
sudo iptables-save > $file
|
||||||
|
cat $file""")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Prov.restoreIpTablesFromFile(file: String? = null) = def {
|
||||||
|
val fileName = file ?: cmd("ls -r a* | head -1\n").out
|
||||||
|
fileName?.let { cmd("sudo iptables-restore < $file") }
|
||||||
|
?: ProvResult(false, err = "File to restore not found.")
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nexus
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.containerRuns
|
||||||
|
import org.domaindrivenarchitecture.provs.core.remote
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.fileExists
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.user.base.createUser
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.certbot.provisionCertbot
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base.NginxConf
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base.nginxReverseProxyHttpConfig
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.provisionNginxStandAlone
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provisions sonatype nexus in a docker container.
|
||||||
|
* If you would want nexus to be accessible directly from the internet (e.g. for test or demo reasons)
|
||||||
|
* set parameter portAccessibleFromNetwork to true.
|
||||||
|
*/
|
||||||
|
fun Prov.provisionNexusWithDocker(portAccessibleFromNetwork: Boolean = false) = requireAll {
|
||||||
|
// https://blog.sonatype.com/sonatype-nexus-installation-using-docker
|
||||||
|
// https://medium.com/@AhGh/how-to-setup-sonatype-nexus-3-repository-manager-using-docker-7ff89bc311ce
|
||||||
|
aptInstall("docker.io")
|
||||||
|
|
||||||
|
if (!containerRuns("nexus")) {
|
||||||
|
val volume = "nexus-data"
|
||||||
|
if (!cmdNoEval("docker volume inspect $volume").success) {
|
||||||
|
cmd("docker volume create --name $volume")
|
||||||
|
}
|
||||||
|
cmd("sudo docker run -d --restart unless-stopped -p 8081:8081 --name nexus -v nexus-data:/nexus-data sonatype/nexus3")
|
||||||
|
|
||||||
|
for (n in 0..3) {
|
||||||
|
if (fileExists("/var/lib/docker/volumes/$volume/_data/admin.password", sudo = true)) {
|
||||||
|
val res = cmd("sudo cat /var/lib/docker/volumes/$volume/_data/admin.password")
|
||||||
|
println("Admin Password:" + res.out)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
Thread.sleep(20000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!portAccessibleFromNetwork) {
|
||||||
|
val netIf = getDefaultNetworkingInterface()
|
||||||
|
netIf?.also {
|
||||||
|
val iptablesParameters = "DOCKER-USER -i $it ! -s 127.0.0.1 -j DROP"
|
||||||
|
if (!cmdNoEval("sudo iptables -C $iptablesParameters").success) {
|
||||||
|
cmd("sudo iptables -I $iptablesParameters")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ProvResult(true) // dummy
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Prov.getDefaultNetworkingInterface(): String? {
|
||||||
|
return cmd("route | grep \"^default\" | grep -o \"[^ ]*\$\"\n").out?.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provisions sonatype nexus on the specified host.
|
||||||
|
* Creates user "nexus" on the remote system.
|
||||||
|
* Installs nexus in a docker container behind an nginx reverse proxy with ssl using letsencrypt certificates.
|
||||||
|
*
|
||||||
|
* To run this method it is required to have ssl root access to the host.
|
||||||
|
*/
|
||||||
|
@Suppress("unused") // to be used externally
|
||||||
|
fun provisionNexusServer(serverName: String, certbotEmail: String) {
|
||||||
|
val userName = "nexus" + 7
|
||||||
|
remote(serverName, "root").def {
|
||||||
|
createUser(userName, copyAuthorizedKeysFromCurrentUser = true, sudo = true)
|
||||||
|
}
|
||||||
|
remote(serverName, userName).requireAll {
|
||||||
|
provisionNexusWithDocker()
|
||||||
|
|
||||||
|
if (provisionNginxStandAlone(NginxConf.nginxReverseProxyHttpConfig(serverName)).success) {
|
||||||
|
|
||||||
|
cmd("sudo cat /etc/nginx/nginx.conf")
|
||||||
|
|
||||||
|
provisionCertbot(serverName, certbotEmail, "--nginx")
|
||||||
|
|
||||||
|
optional {
|
||||||
|
cmd("sudo cat /etc/nginx/nginx.conf")
|
||||||
|
cmd("sudo sed -i 's/X-Forwarded-Proto \"http\"/X-Forwarded-Proto \"https\"/g' /etc/nginx/nginx.conf")
|
||||||
|
cmd("sudo systemctl reload nginx")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ProvResult(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nexus.base
|
||||||
|
|
||||||
|
fun reverseProxyConfigHttpPort80(serverName: String): String {
|
||||||
|
// see https://help.sonatype.com/repomanager3/installation/run-behind-a-reverse-proxy
|
||||||
|
return """
|
||||||
|
events {} # event context have to be defined to consider config valid
|
||||||
|
|
||||||
|
http {
|
||||||
|
|
||||||
|
proxy_send_timeout 120;
|
||||||
|
proxy_read_timeout 300;
|
||||||
|
proxy_buffering off;
|
||||||
|
keepalive_timeout 5 5;
|
||||||
|
tcp_nodelay on;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name $serverName;
|
||||||
|
|
||||||
|
# allow large uploads of files
|
||||||
|
client_max_body_size 1G;
|
||||||
|
|
||||||
|
# optimize downloading files larger than 1G
|
||||||
|
#proxy_max_temp_file_size 2G;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# Use IPv4 upstream address instead of DNS name to avoid attempts by nginx to use IPv6 DNS lookup
|
||||||
|
proxy_pass http://127.0.0.1:8081/;
|
||||||
|
proxy_set_header Host ${'$'}host;
|
||||||
|
proxy_set_header X-Real-IP ${'$'}remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For ${'$'}proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto "http";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun reverseProxyConfigSsl(serverName: String, ssl_certificate: String? = null, ssl_certificate_key: String? = null): String {
|
||||||
|
// see https://help.sonatype.com/repomanager3/installation/run-behind-a-reverse-proxy
|
||||||
|
|
||||||
|
val sslCertificateEntry = ssl_certificate?.let { "ssl_certificate $ssl_certificate;" } ?: "ssl_certificate /etc/letsencrypt/live/$serverName/fullchain.pem;"
|
||||||
|
val sslCertificateKeyEntry = ssl_certificate?.let { "ssl_certificate_key $ssl_certificate_key;" } ?: "ssl_certificate_key /etc/letsencrypt/live/$serverName/privkey.pem"
|
||||||
|
|
||||||
|
return """
|
||||||
|
events {} # event context have to be defined to consider config valid
|
||||||
|
|
||||||
|
http {
|
||||||
|
|
||||||
|
proxy_send_timeout 120;
|
||||||
|
proxy_read_timeout 300;
|
||||||
|
proxy_buffering off;
|
||||||
|
keepalive_timeout 5 5;
|
||||||
|
tcp_nodelay on;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen *:443 ssl;
|
||||||
|
server_name $serverName;
|
||||||
|
|
||||||
|
# allow large uploads of files
|
||||||
|
client_max_body_size 1G;
|
||||||
|
|
||||||
|
# optimize downloading files larger than 1G
|
||||||
|
# proxy_max_temp_file_size 2G;
|
||||||
|
|
||||||
|
$sslCertificateEntry
|
||||||
|
$sslCertificateKeyEntry
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# Use IPv4 upstream address instead of DNS name to avoid attempts by nginx to use IPv6 DNS lookup
|
||||||
|
proxy_pass http://127.0.0.1:8081/;
|
||||||
|
proxy_set_header Host ${'$'}host;
|
||||||
|
proxy_set_header X-Real-IP ${'$'}remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For ${'$'}proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto "https";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.core.remote
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createFile
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.fileExists
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base.NginxConf
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base.createNginxLocationFolders
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
|
||||||
|
internal const val configFile = "/etc/nginx/nginx.conf"
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.provisionNginxStandAlone(config: NginxConf? = null) = requireAll {
|
||||||
|
|
||||||
|
aptInstall("nginx")
|
||||||
|
|
||||||
|
createNginxLocationFolders()
|
||||||
|
|
||||||
|
if (config != null) {
|
||||||
|
if (fileExists(configFile)) {
|
||||||
|
cmd("sudo mv $configFile $configFile-orig")
|
||||||
|
}
|
||||||
|
createFile(configFile, config.conf, sudo = true)
|
||||||
|
val configCheck = cmd("sudo nginx -t")
|
||||||
|
if (configCheck.success) {
|
||||||
|
cmd("sudo service nginx restart")
|
||||||
|
} else {
|
||||||
|
ProvResult(false, err = "Nginx config is incorrect:\n" + configCheck.err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ProvResult(true) // dummy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun provisionRemote(vararg args: String) {
|
||||||
|
if (args.size != 2) {
|
||||||
|
println("Pls specify host and user for remote installation of nginx.")
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
remote(args[0], args[1]).provisionNginxStandAlone()
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
|
||||||
|
fun Prov.nginxAddBasicAuth(user: String, password: Secret) = requireAll {
|
||||||
|
aptInstall("apache2-utils")
|
||||||
|
val passwordFile = "/etc/nginx/.htpasswd"
|
||||||
|
cmdNoLog("sudo htpasswd -b -c $passwordFile $user ${password.plain()}")
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base
|
||||||
|
|
||||||
|
class NginxConf(val conf: String = NGINX_MINIMAL_CONF) {
|
||||||
|
companion object {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const val NGINX_MINIMAL_CONF = """
|
||||||
|
events {}
|
||||||
|
|
||||||
|
http {
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 200 'Hi from nginx!';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("unused") // use later
|
||||||
|
fun NginxConf.Companion.nginxHttpConf(
|
||||||
|
serverName: String = "localhost"
|
||||||
|
): NginxConf {
|
||||||
|
return NginxConf(
|
||||||
|
"""
|
||||||
|
events {}
|
||||||
|
|
||||||
|
http {
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name $serverName;
|
||||||
|
|
||||||
|
include /etc/nginx/locations-enabled/port80*$locationsFileExtension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun NginxConf.Companion.nginxHttpsConfWithLocationFiles(
|
||||||
|
sslCertificate: String = "/etc/nginx/ssl/cert/selfsigned.crt",
|
||||||
|
sslCertificateKey: String = "/etc/nginx/ssl/private/selfsigned.key"
|
||||||
|
): NginxConf {
|
||||||
|
return NginxConf(
|
||||||
|
"""
|
||||||
|
events {}
|
||||||
|
|
||||||
|
http {
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
ssl_certificate $sslCertificate;
|
||||||
|
ssl_certificate_key $sslCertificateKey;
|
||||||
|
|
||||||
|
include /etc/nginx/locations-enabled/port443*$locationsFileExtension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("unused") // use later
|
||||||
|
fun NginxConf.Companion.nginxReverseProxySslConfig(
|
||||||
|
serverName: String,
|
||||||
|
ssl_certificate: String? = null,
|
||||||
|
ssl_certificate_key: String? = null
|
||||||
|
): NginxConf {
|
||||||
|
// see https://help.sonatype.com/repomanager3/installation/run-behind-a-reverse-proxy
|
||||||
|
|
||||||
|
val sslCertificateEntry = ssl_certificate?.let { "ssl_certificate $ssl_certificate;" }
|
||||||
|
?: "ssl_certificate /etc/letsencrypt/live/$serverName/fullchain.pem;"
|
||||||
|
val sslCertificateKeyEntry = ssl_certificate?.let { "ssl_certificate_key $ssl_certificate_key;" }
|
||||||
|
?: "ssl_certificate_key /etc/letsencrypt/live/$serverName/privkey.pem"
|
||||||
|
|
||||||
|
return NginxConf(
|
||||||
|
"""
|
||||||
|
events {} # event context have to be defined to consider config valid
|
||||||
|
|
||||||
|
http {
|
||||||
|
|
||||||
|
proxy_send_timeout 120;
|
||||||
|
proxy_read_timeout 300;
|
||||||
|
proxy_buffering off;
|
||||||
|
keepalive_timeout 5 5;
|
||||||
|
tcp_nodelay on;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen *:443 ssl;
|
||||||
|
server_name $serverName;
|
||||||
|
|
||||||
|
# allow large uploads of files
|
||||||
|
client_max_body_size 1G;
|
||||||
|
|
||||||
|
# optimize downloading files larger than 1G
|
||||||
|
#proxy_max_temp_file_size 2G;
|
||||||
|
|
||||||
|
$sslCertificateEntry
|
||||||
|
$sslCertificateKeyEntry
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# Use IPv4 upstream address instead of DNS name to avoid attempts by nginx to use IPv6 DNS lookup
|
||||||
|
proxy_pass http://127.0.0.1:8081/;
|
||||||
|
proxy_set_header Host ${'$'}host;
|
||||||
|
proxy_set_header X-Real-IP ${'$'}remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For ${'$'}proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto "https";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("unused") // use later
|
||||||
|
fun NginxConf.Companion.nginxReverseProxyHttpConfig(
|
||||||
|
serverName: String
|
||||||
|
): NginxConf {
|
||||||
|
// see https://help.sonatype.com/repomanager3/installation/run-behind-a-reverse-proxy
|
||||||
|
|
||||||
|
return NginxConf(
|
||||||
|
"""
|
||||||
|
events {} # event context have to be defined to consider config valid
|
||||||
|
|
||||||
|
http {
|
||||||
|
|
||||||
|
proxy_send_timeout 120;
|
||||||
|
proxy_read_timeout 300;
|
||||||
|
proxy_buffering off;
|
||||||
|
keepalive_timeout 5 5;
|
||||||
|
tcp_nodelay on;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen *:80;
|
||||||
|
server_name $serverName;
|
||||||
|
|
||||||
|
# allow large uploads of files
|
||||||
|
client_max_body_size 1G;
|
||||||
|
|
||||||
|
# optimize downloading files larger than 1G
|
||||||
|
#proxy_max_temp_file_size 2G;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# Use IPv4 upstream address instead of DNS name to avoid attempts by nginx to use IPv6 DNS lookup
|
||||||
|
proxy_pass http://127.0.0.1:8081/;
|
||||||
|
proxy_set_header Host ${'$'}host;
|
||||||
|
proxy_set_header X-Real-IP ${'$'}remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For ${'$'}proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto "https";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.*
|
||||||
|
|
||||||
|
|
||||||
|
internal const val locationsAvailableDir = "/etc/nginx/locations-available/"
|
||||||
|
internal const val locationsEnabledDir = "/etc/nginx/locations-enabled/"
|
||||||
|
internal const val locationsFileExtension = ".locations"
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.createNginxLocationFolders() = requireAll {
|
||||||
|
createDirs(locationsEnabledDir, sudo = true)
|
||||||
|
createDirs(locationsAvailableDir, sudo = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.nginxIncludeLocationFolders() = requireAll {
|
||||||
|
replaceTextInFile("/etc/nginx/nginx.conf", "listen 80;\n",
|
||||||
|
"""listen 80;
|
||||||
|
include ${locationsAvailableDir}port80*$locationsFileExtension;
|
||||||
|
include ${locationsEnabledDir}port443*$locationsFileExtension;
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.nginxAddLocation(port: String, locationFileName: String, urlPath: String, content: String) = requireAll {
|
||||||
|
|
||||||
|
val locationConf = """location $urlPath {""" +
|
||||||
|
content +
|
||||||
|
"\n}"
|
||||||
|
|
||||||
|
if (!dirExists(locationsAvailableDir, sudo = true)) {
|
||||||
|
createNginxLocationFolders()
|
||||||
|
}
|
||||||
|
|
||||||
|
createFile("${locationsAvailableDir}port${port}_$locationFileName$locationsFileExtension", locationConf, sudo = true)
|
||||||
|
if (!fileExists("${locationsEnabledDir}port${port}_$locationFileName$locationsFileExtension", sudo = true)) {
|
||||||
|
cmd("sudo ln -s ${locationsAvailableDir}port${port}_$locationFileName$locationsFileExtension ${locationsEnabledDir}port${port}_$locationFileName$locationsFileExtension ")
|
||||||
|
} else {
|
||||||
|
ProvResult(true)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createDirs
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.provisionNginxStandAlone
|
||||||
|
|
||||||
|
|
||||||
|
internal val certificateName = "selfsigned"
|
||||||
|
internal val sslDays = 365
|
||||||
|
val dirSslCert="/etc/nginx/ssl/cert"
|
||||||
|
val dirSslKey="/etc/nginx/ssl/private"
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.nginxCreateSelfSignedCertificate(
|
||||||
|
country: String = "DE",
|
||||||
|
state: String = "test",
|
||||||
|
locality: String = "test",
|
||||||
|
organization: String = "test",
|
||||||
|
organizationalUnit: String = "test",
|
||||||
|
commonName: String = "test",
|
||||||
|
email : String = "test@test.net"
|
||||||
|
) = def {
|
||||||
|
// inspired by https://gist.github.com/adrianorsouza/2bbfe5e197ce1c0b97c8
|
||||||
|
createDirs(dirSslCert, sudo = true)
|
||||||
|
createDirs(dirSslKey, sudo = true)
|
||||||
|
cmd("cd $dirSslKey && sudo openssl req -x509 -nodes -newkey rsa:2048 -keyout $certificateName.key -out $certificateName.crt -days $sslDays -subj \"/C=$country/ST=$state/L=$locality/O=$organization/OU=$organizationalUnit/CN=$commonName/emailAddress=$email\"")
|
||||||
|
cmd("sudo mv $dirSslKey/$certificateName.crt $dirSslCert/")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.configureNginxWithSelfSignedCertificate() = def {
|
||||||
|
// todo: should not call provisionNginxStandAlone, which is defined in the package above
|
||||||
|
provisionNginxStandAlone(NginxConf.nginxReverseProxySslConfig("localhost",
|
||||||
|
dirSslCert+"/"+ certificateName + ".crt",
|
||||||
|
dirSslKey + "/" + certificateName + ".key"))
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.prometheus
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.prometheus.base.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provisions prometheus monitoring.
|
||||||
|
* If running behind an nginx, pls specify the hostname in parameter nginxHost (e.g. mydomain.com).
|
||||||
|
* To run it without nodeExporter (which provides system data to prometheus), set withNodeExporter to false.
|
||||||
|
*/
|
||||||
|
fun Prov.provisionPrometheusDocker(nginxHost: String? = null, withNodeExporter: Boolean = true) = def {
|
||||||
|
configurePrometheusDocker()
|
||||||
|
if (withNodeExporter) {
|
||||||
|
installNodeExporter()
|
||||||
|
runNodeExporter()
|
||||||
|
addNodeExporterToPrometheusConf()
|
||||||
|
}
|
||||||
|
runPrometheusDocker(nginxHost)
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.prometheus.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createDir
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createFile
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.fileContainsText
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.replaceTextInFile
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.user.base.whoami
|
||||||
|
|
||||||
|
|
||||||
|
internal val defaultInstallationDir = "/usr/local/bin/"
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.installNodeExporter() = requireAll {
|
||||||
|
// inspired by https://devopscube.com/monitor-linux-servers-prometheus-node-exporter/ and
|
||||||
|
// https://www.howtoforge.com/tutorial/how-to-install-prometheus-and-node-exporter-on-centos-8/#step-install-and-configure-nodeexporter
|
||||||
|
val downloadFileBasename = "node_exporter-1.0.1.linux-amd64"
|
||||||
|
val downloadFile = "$downloadFileBasename.tar.gz"
|
||||||
|
val downloadPath = "~/tmp/"
|
||||||
|
val fqFile = downloadPath + downloadFile
|
||||||
|
|
||||||
|
aptInstall("curl")
|
||||||
|
createDir("tmp")
|
||||||
|
sh(
|
||||||
|
"""
|
||||||
|
cd tmp && curl -LO https://github.com/prometheus/node_exporter/releases/download/v1.0.1/$downloadFile --output $downloadFile
|
||||||
|
cd tmp && tar -xvf $fqFile -C $downloadPath
|
||||||
|
|
||||||
|
sudo mv $downloadPath$downloadFileBasename/node_exporter $defaultInstallationDir"""
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.runNodeExporter() = def {
|
||||||
|
createFile("/etc/systemd/system/node_exporter.service", nodeExporterServiceConf(whoami()?:"nouserfound"), sudo = true)
|
||||||
|
|
||||||
|
sh("""
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
|
||||||
|
# start the node_exporter service and enable it to launch everytime at system startup.
|
||||||
|
sudo systemctl start node_exporter
|
||||||
|
sudo systemctl enable node_exporter
|
||||||
|
|
||||||
|
# check if running
|
||||||
|
sudo systemctl status node_exporter --no-pager -l
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.addNodeExporterToPrometheusConf (
|
||||||
|
prometheusConf: String = "/etc/prometheus/prometheus.yml",
|
||||||
|
sudo: Boolean = true
|
||||||
|
) = requireAll {
|
||||||
|
val prometheusConfNodeExporter = """
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: 'node_exporter'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['172.17.0.1:9100']
|
||||||
|
"""
|
||||||
|
if (!fileContainsText(prometheusConf, "- job_name: 'node_exporter'", sudo)) {
|
||||||
|
replaceTextInFile(prometheusConf, "\nscrape_configs:\n", prometheusConfNodeExporter)
|
||||||
|
}
|
||||||
|
// cmd("sudo systemctl restart prometheus") for standalone
|
||||||
|
cmd("sudo docker restart prometheus")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun nodeExporterServiceConf(user: String, installationDir: String = defaultInstallationDir): String {
|
||||||
|
return """
|
||||||
|
[Unit]
|
||||||
|
Description=Node Exporter
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=$user
|
||||||
|
ExecStart=${installationDir}node_exporter
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
"""
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.prometheus.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.containerRuns
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createDirs
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createFile
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
|
||||||
|
|
||||||
|
internal val configDir = "/etc/prometheus/"
|
||||||
|
internal val configFile = "prometheus.yml"
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.configurePrometheusDocker(config: String = prometheusDefaultConfig) = requireAll {
|
||||||
|
createDirs(configDir, sudo = true)
|
||||||
|
createFile(configDir + configFile, config, sudo = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.runPrometheusDocker(nginxHost: String? = null) = requireAll {
|
||||||
|
aptInstall("docker.io")
|
||||||
|
|
||||||
|
val containerName = "prometheus"
|
||||||
|
|
||||||
|
if (containerRuns(containerName)) {
|
||||||
|
cmd("sudo docker restart $containerName")
|
||||||
|
} else {
|
||||||
|
if (nginxHost == null) {
|
||||||
|
cmd(
|
||||||
|
"sudo docker run -d -p 9090:9090 " +
|
||||||
|
" --name $containerName " +
|
||||||
|
" --restart on-failure:1" +
|
||||||
|
" -v prometheus-data:/prometheus" +
|
||||||
|
" -v $configDir$configFile:/etc/prometheus/prometheus.yml " +
|
||||||
|
" prom/prometheus"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
cmd(
|
||||||
|
"sudo docker run -d -p 9090:9090 " +
|
||||||
|
" --name $containerName " +
|
||||||
|
" --restart on-failure:1" +
|
||||||
|
" -v prometheus-data:/prometheus" +
|
||||||
|
" -v $configDir$configFile:/etc/prometheus/prometheus.yml " +
|
||||||
|
" prom/prometheus --config.file=/etc/prometheus/prometheus.yml --storage.tsdb.path=/prometheus " +
|
||||||
|
"--web.console.libraries=/usr/share/prometheus/console_libraries " +
|
||||||
|
"--web.console.templates=/usr/share/prometheus/consoles " +
|
||||||
|
"--web.external-url=http://$nginxHost/prometheus"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private const val prometheusDefaultConfig =
|
||||||
|
"""
|
||||||
|
global:
|
||||||
|
scrape_interval: 15s # By default, scrape targets every 15 seconds.
|
||||||
|
|
||||||
|
# Attach these labels to any time series or alerts when communicating with
|
||||||
|
# external systems (federation, remote storage, Alertmanager).
|
||||||
|
external_labels:
|
||||||
|
monitor: 'codelab-monitor'
|
||||||
|
|
||||||
|
# A scrape configuration containing exactly one endpoint to scrape:
|
||||||
|
# Here it's Prometheus itself.
|
||||||
|
scrape_configs:
|
||||||
|
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
|
||||||
|
- job_name: 'prometheus'
|
||||||
|
|
||||||
|
# Override the global default and scrape targets from this job every 5 seconds.
|
||||||
|
scrape_interval: 5s
|
||||||
|
|
||||||
|
static_configs:
|
||||||
|
- targets: ['localhost:9090']
|
||||||
|
"""
|
|
@ -0,0 +1,5 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.prometheus.base
|
||||||
|
|
||||||
|
val prometheusNginxConfig = """
|
||||||
|
proxy_pass http://localhost:9090/prometheus;
|
||||||
|
"""
|
|
@ -0,0 +1,155 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.*
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.RemoteProcessor
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base.*
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.git.provisionGit
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstallFromPpa
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptPurge
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.KeyPair
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.base.gpgFingerprint
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.provisionKeysCurrentUser
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.secret.secretSources.PromptSecretSource
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.user.base.currentUserCanSudo
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.user.base.makeUserSudoerWithNoSudoPasswordRequired
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.user.base.whoami
|
||||||
|
import java.net.InetAddress
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
|
||||||
|
enum class WorkplaceType {
|
||||||
|
MINIMAL, OFFICE, IDE
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provisions software and configurations for a personal workplace.
|
||||||
|
* Offers the possibility to choose between different types.
|
||||||
|
* Type OFFICE installs office-related software like Thunderbird, LibreOffice, and much more.
|
||||||
|
* Type IDE provides additional software for a development environment, such as Visual Studio Code, IntelliJ, etc.
|
||||||
|
*
|
||||||
|
* Prerequisites: user must be sudoer. If password is required for user to execute sudo, then also parameter userPassword must be provided
|
||||||
|
*
|
||||||
|
* @param workplaceType
|
||||||
|
* @param userPassword only needs to be provided if user cannot sudo without password
|
||||||
|
*/
|
||||||
|
fun Prov.provisionWorkplace(
|
||||||
|
workplaceType: WorkplaceType = WorkplaceType.MINIMAL,
|
||||||
|
ssh: KeyPair? = null,
|
||||||
|
gpg: KeyPair? = null,
|
||||||
|
gitUserName: String? = null,
|
||||||
|
gitEmail: String? = null,
|
||||||
|
userPassword: Secret? = null
|
||||||
|
) = requireAll {
|
||||||
|
|
||||||
|
userPassword?.also { makeUserSudoerWithNoSudoPasswordRequired(it) }
|
||||||
|
|
||||||
|
if (!currentUserCanSudo()) {
|
||||||
|
throw Exception("Current user ${whoami()} cannot execute sudo without a password, but he must be able to in order to provisionWorkplace")
|
||||||
|
}
|
||||||
|
|
||||||
|
aptInstall("ssh gnupg curl git")
|
||||||
|
|
||||||
|
provisionKeysCurrentUser(gpg, ssh)
|
||||||
|
provisionGit(gitUserName ?: whoami(), gitEmail, gpg?.let { gpgFingerprint(it.publicKey.plain()) })
|
||||||
|
|
||||||
|
installVirtualBoxGuestAdditions()
|
||||||
|
|
||||||
|
aptPurge("remove-power-management xfce4-power-manager " +
|
||||||
|
"xfce4-power-manager-plugins xfce4-power-manager-data")
|
||||||
|
aptPurge("abiword gnumeric")
|
||||||
|
aptPurge("popularity-contest")
|
||||||
|
|
||||||
|
configureNoSwappiness()
|
||||||
|
|
||||||
|
if (workplaceType == WorkplaceType.OFFICE || workplaceType == WorkplaceType.IDE) {
|
||||||
|
aptInstall("seahorse")
|
||||||
|
aptInstall(BASH_UTILS)
|
||||||
|
aptInstall(OS_ANALYSIS)
|
||||||
|
aptInstall(ZIP_UTILS)
|
||||||
|
|
||||||
|
aptInstall("firefox chromium-browser")
|
||||||
|
aptInstall("thunderbird libreoffice")
|
||||||
|
aptInstall("xclip")
|
||||||
|
|
||||||
|
installZimWiki()
|
||||||
|
installGopass()
|
||||||
|
aptInstallFromPpa("nextcloud-devs", "client", "nextcloud-client")
|
||||||
|
|
||||||
|
aptInstall("inkscape")
|
||||||
|
aptInstall("dia")
|
||||||
|
|
||||||
|
aptInstall(SPELLCHECKING_DE)
|
||||||
|
|
||||||
|
installRedshift()
|
||||||
|
configureRedshift()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workplaceType == WorkplaceType.IDE) {
|
||||||
|
|
||||||
|
aptInstall(JAVA_JDK)
|
||||||
|
|
||||||
|
aptInstall(OPEN_VPM)
|
||||||
|
aptInstall(OPENCONNECT)
|
||||||
|
aptInstall(VPNC)
|
||||||
|
|
||||||
|
installDocker()
|
||||||
|
|
||||||
|
// IDEs
|
||||||
|
cmd("sudo snap install intellij-idea-community --classic")
|
||||||
|
installVSC("python", "clojure")
|
||||||
|
}
|
||||||
|
|
||||||
|
ProvResult(true) // dummy
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provisions a workplace on a remote machine.
|
||||||
|
* Prerequisite: you have built the uberjar by ./gradlew uberJarLatest
|
||||||
|
* The remote host and remote user are specified by args parameters.
|
||||||
|
* The first argument specifies hostName or IP-Address of the remote machine,
|
||||||
|
* the second argument defines the user on the remote machine for whom the workplace is provisioned;
|
||||||
|
* You can invoke this method e.g. using the jar-file from the project root by:
|
||||||
|
* java -jar build/libs/provs-extensions-uber.jar org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.ProvisionWorkplaceKt provisionRemote <ip> <user>
|
||||||
|
* You will be prompted for the password of the remote user.
|
||||||
|
*
|
||||||
|
* @param args host and userName of the remote machine as the first resp. second argument
|
||||||
|
*/
|
||||||
|
fun provisionRemote(args: Array<String>) {
|
||||||
|
if (args.size != 2) {
|
||||||
|
println("Please specify host and user.")
|
||||||
|
exitProcess(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val host = InetAddress.getByName(args[0])
|
||||||
|
val userName = args[1]
|
||||||
|
val pwSecret = PromptSecretSource("Password for user $userName on $host").secret()
|
||||||
|
val pwFromSecret = Password(pwSecret.plain())
|
||||||
|
|
||||||
|
val config = readWorkplaceConfigFromFile() ?: WorkplaceConfig()
|
||||||
|
Prov.newInstance(RemoteProcessor(host, userName, pwFromSecret), OS.LINUX.name).provisionWorkplace(
|
||||||
|
config.type,
|
||||||
|
config.ssh?.keyPair(),
|
||||||
|
config.gpg?.keyPair(),
|
||||||
|
config.gitUserName,
|
||||||
|
config.gitEmail,
|
||||||
|
pwFromSecret
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provisions a workplace on a remote machine by calling method provisionRemote.
|
||||||
|
*
|
||||||
|
* @ see #provisionRemote(args: Array<String>)
|
||||||
|
*
|
||||||
|
* You can invoke this method e.g. using the jar-file from the project root by:
|
||||||
|
* java -jar build/libs/provs-ext-latest.jar workplace.WorkplaceKt main
|
||||||
|
*
|
||||||
|
* @param args host and userName of the remote machine as first resp. second argument
|
||||||
|
*/
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
provisionRemote(args = args)
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.KeyPairSource
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.secret.SecretSource
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.secret.SecretSourceType
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.secret.SecretSupplier
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.secret.secretSources.PlainSecretSource
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.io.*
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class WorkplaceConfig(
|
||||||
|
val type: WorkplaceType = WorkplaceType.MINIMAL,
|
||||||
|
val ssh: KeyPairSource? = null,
|
||||||
|
val gpg: KeyPairSource? = null,
|
||||||
|
val gitUserName: String? = null,
|
||||||
|
val gitEmail: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// -------------------------------------------- file methods ------------------------------------
|
||||||
|
fun readWorkplaceConfigFromFile(filename: String = "WorkplaceConfig.json"): WorkplaceConfig? {
|
||||||
|
val file = File(filename)
|
||||||
|
return if (file.exists())
|
||||||
|
try {
|
||||||
|
// read from file
|
||||||
|
val inputAsString = BufferedReader(FileReader(filename)).use { it.readText() }
|
||||||
|
|
||||||
|
return Json.decodeFromString(WorkplaceConfig.serializer(), inputAsString)
|
||||||
|
} catch (e: FileNotFoundException) {
|
||||||
|
null
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun writeWorkplaceConfigToFile(config: WorkplaceConfig) {
|
||||||
|
val fileName = "WorkplaceConfig.json"
|
||||||
|
|
||||||
|
FileWriter(fileName).use { it.write(Json.encodeToString(WorkplaceConfig.serializer(), config)) }
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
|
||||||
|
fun Prov.installDocker() = def {
|
||||||
|
aptInstall("containerd docker.io")
|
||||||
|
if (!chk("getent group docker")) {
|
||||||
|
cmd("sudo groupadd docker")
|
||||||
|
}
|
||||||
|
cmd("sudo gpasswd -a \$USER docker")
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createDir
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.web.base.downloadFromURL
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.installFakturama() = def {
|
||||||
|
createDir("/tmp", sudo = true)
|
||||||
|
downloadFromURL( "https://files.fakturama.info/release/v2.1.1/Installer_Fakturama_linux_x64_2.1.1b.deb", "fakturama.deb", "/tmp")
|
||||||
|
cmd("sudo dpkg -i fakturama.deb", "/tmp")
|
||||||
|
|
||||||
|
createDir("/opt/fakturama", sudo = true)
|
||||||
|
val filename = "Handbuch-Fakturama_2.1.1.pdf"
|
||||||
|
downloadFromURL( "https://files.fakturama.info/release/v2.1.1/Handbuch-Fakturama_2.1.1.pdf", filename, "/tmp")
|
||||||
|
cmd("sudo mv /tmp/$filename /opt/fakturama")
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createDir
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createDirs
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createFile
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.userHome
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.isPackageInstalled
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.installGopass(version: String = "1.12.7", enforceVersion: Boolean = false) = def {
|
||||||
|
if (isPackageInstalled("gopass") && !enforceVersion) {
|
||||||
|
ProvResult(true)
|
||||||
|
} else {
|
||||||
|
// install required dependencies
|
||||||
|
aptInstall("rng-tools gnupg2 git")
|
||||||
|
aptInstall("curl")
|
||||||
|
|
||||||
|
sh(
|
||||||
|
"""
|
||||||
|
curl -L https://github.com/gopasspw/gopass/releases/download/v${version}/gopass_${version}_linux_amd64.deb -o gopass_${version}_linux_amd64.deb
|
||||||
|
sudo dpkg -i gopass_${version}_linux_amd64.deb
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
gopassEnsureVersion(version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.configureGopass(gopassRootFolder: String? = null) = def {
|
||||||
|
val defaultRootFolder = userHome() + ".password-store"
|
||||||
|
val rootFolder = gopassRootFolder ?: defaultRootFolder
|
||||||
|
// use default
|
||||||
|
createDir(rootFolder)
|
||||||
|
createDirs(".config/gopass")
|
||||||
|
createFile("~/.config/gopass/config.yml", gopassConfig(rootFolder))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.gopassMountStore(storeName: String, path: String, indexOfRecepientKey: Int = 0) = def {
|
||||||
|
cmd("printf \"$indexOfRecepientKey\\n\" | gopass mounts add $storeName $path")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal fun gopassConfig(gopassRoot: String): String {
|
||||||
|
return """
|
||||||
|
root:
|
||||||
|
askformore: false
|
||||||
|
autoclip: true
|
||||||
|
autoprint: false
|
||||||
|
autoimport: true
|
||||||
|
autosync: false
|
||||||
|
check_recipient_hash: false
|
||||||
|
cliptimeout: 45
|
||||||
|
concurrency: 1
|
||||||
|
editrecipients: false
|
||||||
|
exportkeys: true
|
||||||
|
nocolor: false
|
||||||
|
noconfirm: true
|
||||||
|
nopager: false
|
||||||
|
notifications: true
|
||||||
|
path: gpgcli-gitcli-fs+file://$gopassRoot
|
||||||
|
recipient_hash:
|
||||||
|
.gpg-id: 3078303637343130344341383141343930350aa69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26
|
||||||
|
safecontent: false
|
||||||
|
usesymbols: false
|
||||||
|
mounts: {}
|
||||||
|
""".trim() + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if gopass is installed and has the given version.
|
||||||
|
*
|
||||||
|
* @param version that is checked; specifies left part of text of installed version, e.g. both "1" and "1.12" will return true if installed version is "1.12.6+8d7a311b9273846bbb618e4bd9ddbae51b1db7b8"
|
||||||
|
*/
|
||||||
|
internal fun Prov.gopassEnsureVersion(version: String) = def {
|
||||||
|
val installedGopassVersion = gopassVersion()
|
||||||
|
if (installedGopassVersion != null && installedGopassVersion.startsWith("gopass " + version)) {
|
||||||
|
ProvResult(true, out = "Required gopass version ($version) matches installed version ($installedGopassVersion)")
|
||||||
|
} else {
|
||||||
|
ProvResult(false, err = "Wrong gopass version. Expected $version but found $installedGopassVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Prov.gopassVersion(): String? {
|
||||||
|
val result = cmdNoEval("gopass -v")
|
||||||
|
return if (!result.success) null else result.out
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createDir
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createDirs
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.userHome
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.isPackageInstalled
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.web.base.downloadFromURL
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.downloadGopassBridge() = def {
|
||||||
|
val version = "0.8.0"
|
||||||
|
val filename = "gopass_bridge-${version}-fx.xpi"
|
||||||
|
val downloadDir = "${userHome()}Downloads/"
|
||||||
|
|
||||||
|
createDirs(downloadDir)
|
||||||
|
downloadFromURL(
|
||||||
|
"-L https://addons.mozilla.org/firefox/downloads/file/3630534/" + filename,
|
||||||
|
downloadDir + filename
|
||||||
|
)
|
||||||
|
// needs manual install with: firefox Downloads/gopass_bridge-0.8.0-fx.xpi
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Prov.installGopassBridgeJsonApi() = def {
|
||||||
|
// see https://github.com/gopasspw/gopass-jsonapi
|
||||||
|
val gopassBridgeVersion = "1.11.1"
|
||||||
|
val requiredGopassVersion = "1.12"
|
||||||
|
val filename = "gopass-jsonapi_${gopassBridgeVersion}_linux_amd64.deb"
|
||||||
|
val downloadUrl = "-L https://github.com/gopasspw/gopass-jsonapi/releases/download/v$gopassBridgeVersion/$filename"
|
||||||
|
val downloadDir = "${userHome()}Downloads"
|
||||||
|
val installedJsonApiVersion = gopassJsonApiVersion()?.trim()
|
||||||
|
|
||||||
|
if (installedJsonApiVersion == null) {
|
||||||
|
if (chk("gopass ls")) {
|
||||||
|
if (gopassEnsureVersion(requiredGopassVersion).success) {
|
||||||
|
aptInstall("git gnupg2") // required dependencies
|
||||||
|
createDir(downloadDir)
|
||||||
|
downloadFromURL(downloadUrl, filename, downloadDir)
|
||||||
|
cmd("dpkg -i " + downloadDir + "/" + filename, sudo = true)
|
||||||
|
} else {
|
||||||
|
ProvResult(
|
||||||
|
false,
|
||||||
|
"Version of currently installed gopass (" + gopassVersion() + ") is incompatible with gopass-jsonapi version to be installed. " +
|
||||||
|
"Please upgrade gopass to version: " + requiredGopassVersion
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addResultToEval(
|
||||||
|
ProvResult(
|
||||||
|
false,
|
||||||
|
"gopass not initialized correctly. You can initialize gopass with: \"gopass init\""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (installedJsonApiVersion.startsWith("gopass-jsonapi version " + gopassBridgeVersion)) {
|
||||||
|
addResultToEval(ProvResult(true, out = "Version $gopassBridgeVersion of gopass-jsonapi is already installed"))
|
||||||
|
} else {
|
||||||
|
addResultToEval(
|
||||||
|
ProvResult(
|
||||||
|
false,
|
||||||
|
err = "gopass-jsonapi (version $gopassBridgeVersion) cannot be installed as version $installedJsonApiVersion is already installed." +
|
||||||
|
" Upgrading gopass-jsonapi is currently not supported by provs."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Prov.configureGopassBridgeJsonApi() = def {
|
||||||
|
if (isPackageInstalled("gopass-jsonapi")) {
|
||||||
|
// configure for firefox and choose default for each:
|
||||||
|
// "Install for all users? [y/N/q]",
|
||||||
|
// "In which path should gopass_wrapper.sh be installed? [/home/testuser/.config/gopass]"
|
||||||
|
// "Wrapper Script for gopass_wrapper.sh ..."
|
||||||
|
cmd("printf \"\\n\\n\\n\" | gopass-jsonapi configure --browser firefox")
|
||||||
|
} else {
|
||||||
|
ProvResult(
|
||||||
|
false,
|
||||||
|
err = "gopass-jsonapi is missing. Gopass-jsonapi must be installed to be able to configure it."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Prov.gopassJsonApiVersion(): String? {
|
||||||
|
val result = cmdNoEval("gopass-jsonapi -v")
|
||||||
|
return if (!result.success) null else result.out
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.addTextToFile
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
fun Prov.configureNoSwappiness() = def {
|
||||||
|
// set swappiness to 0
|
||||||
|
addTextToFile("vm.swappiness=0", File("/etc/sysctl.conf"), sudo = true)
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base
|
||||||
|
|
||||||
|
val OS_ANALYSIS = "lsof strace ncdu iptraf htop iotop iftop"
|
||||||
|
|
||||||
|
val ZIP_UTILS = "p7zip-rar p7zip-full rar unrar zip unzip"
|
||||||
|
|
||||||
|
val BASH_UTILS = "bash-completion"
|
||||||
|
|
||||||
|
val SPELLCHECKING_DE = "hyphen-de hunspell hunspell-de-de"
|
||||||
|
|
||||||
|
val OPEN_VPM = "openvpn network-manager-openvpn network-manager-openvpn-gnome"
|
||||||
|
|
||||||
|
val OPENCONNECT = "openconnect network-manager-openconnect network-manager-openconnect-gnome"
|
||||||
|
|
||||||
|
val VPNC = "vpnc network-manager-vpnc network-manager-vpnc-gnome vpnc-scripts"
|
||||||
|
|
||||||
|
val JAVA_JDK = "openjdk-8-jdk openjdk-11-jdk openjdk-14-jdk"
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createDir
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createFile
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.installRedshift() = def {
|
||||||
|
aptInstall("redshift redshift-gtk")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.configureRedshift() = def {
|
||||||
|
aptInstall("redshift redshift-gtk")
|
||||||
|
|
||||||
|
createDir(".config")
|
||||||
|
createFile("~/.config/redshift.conf", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val config = """
|
||||||
|
[redshift]
|
||||||
|
temp-day=5500
|
||||||
|
temp-night=2700
|
||||||
|
brightness-day=1
|
||||||
|
brightness-night=0.6
|
||||||
|
fade=1
|
||||||
|
|
||||||
|
location-provider=manual
|
||||||
|
|
||||||
|
[manual]
|
||||||
|
lat=48.783333
|
||||||
|
lon=9.1833334
|
||||||
|
""".trimIndent()
|
|
@ -0,0 +1,75 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.installVSC(vararg options: String) = requireAll {
|
||||||
|
val clojureExtensions =
|
||||||
|
arrayListOf("betterthantomorrow.calva", "martinklepsch.clojure-joker-linter", "DavidAnson.vscode-markdownlint")
|
||||||
|
val pythonExtensions = arrayListOf("ms-python.python")
|
||||||
|
|
||||||
|
prerequisitesVSCinstall()
|
||||||
|
|
||||||
|
installVSCPackage()
|
||||||
|
|
||||||
|
if (options.contains("clojure")) {
|
||||||
|
installExtensions(clojureExtensions)
|
||||||
|
}
|
||||||
|
if (options.contains("python")) {
|
||||||
|
installExtensions(pythonExtensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
provisionAdditionalTools()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun Prov.prerequisitesVSCinstall() = def {
|
||||||
|
aptInstall("curl gpg unzip apt-transport-https")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("unused") // only required for installation of vscode via apt
|
||||||
|
private fun Prov.configurePackageManagerForVsc() = requireAll {
|
||||||
|
// see https://code.visualstudio.com/docs/setup/linux
|
||||||
|
// alternatively install with snapd (but this cannot be tested within docker as snapd within docker is not working/supported)
|
||||||
|
|
||||||
|
sh(
|
||||||
|
"""
|
||||||
|
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg
|
||||||
|
sudo install -o root -g root -m 644 packages.microsoft.gpg /etc/apt/trusted.gpg.d/
|
||||||
|
sudo sh -c 'echo \"deb [arch=amd64 signed-by=/etc/apt/trusted.gpg.d/packages.microsoft.gpg] https://packages.microsoft.com/repos/vscode stable main\" > /etc/apt/sources.list.d/vscode.list'
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
aptInstall("apt-transport-https")
|
||||||
|
aptInstall("code")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun Prov.installVSCPackage() = def {
|
||||||
|
cmd("sudo snap install code --classic")
|
||||||
|
|
||||||
|
// to install via apt use:
|
||||||
|
// configurePackageManagerForVsc()
|
||||||
|
// aptInstall("code")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun Prov.installExtensions(extensions: List<String>) = optional {
|
||||||
|
var res = ProvResult(true)
|
||||||
|
for (ext in extensions) {
|
||||||
|
res = cmd("code --install-extension $ext")
|
||||||
|
}
|
||||||
|
res
|
||||||
|
// Settings can be found at $HOME/.config/Code/User/settings.json
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun Prov.provisionAdditionalTools() = requireAll {
|
||||||
|
// Joker
|
||||||
|
cmd("curl -Lo joker-0.12.2-linux-amd64.zip https://github.com/candid82/joker/releases/download/v0.12.2/joker-0.12.2-linux-amd64.zip")
|
||||||
|
cmd("unzip joker-0.12.2-linux-amd64.zip")
|
||||||
|
cmd("sudo mv joker /usr/local/bin/")
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.user.base.whoami
|
||||||
|
|
||||||
|
fun Prov.installVirtualBoxGuestAdditions() = def {
|
||||||
|
// if running in a VirtualBox vm
|
||||||
|
if (!chk("lspci | grep VirtualBox")) {
|
||||||
|
return@def ProvResult(true, "Not running in a VirtualBox")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chk("VBoxService --version")) {
|
||||||
|
return@def ProvResult(true, "VBoxService already installed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// install guest additions
|
||||||
|
cmd("sudo add-apt-repository multiverse")
|
||||||
|
aptInstall("virtualbox-guest-x11") // virtualbox-guest-dkms")
|
||||||
|
// and add user to group vboxsf e.g. to be able to use shared folders
|
||||||
|
whoami()?.let { cmd("sudo usermod -G vboxsf -a " + it) }
|
||||||
|
?: ProvResult(true)
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstallFromPpa
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.isPackageInstalled
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.installZimWiki() = def {
|
||||||
|
if (isPackageInstalled("zim")) {
|
||||||
|
ProvResult(true, out = "zim already installed.")
|
||||||
|
} else {
|
||||||
|
aptInstallFromPpa("jaap.karssenberg", "zim", "zim")
|
||||||
|
aptInstall("python3-gtkspellcheck")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.filesystem.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.*
|
||||||
|
import org.domaindrivenarchitecture.provs.core.platforms.SHELL
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.fileExists(file: String, sudo: Boolean = false): Boolean {
|
||||||
|
return cmdNoEval((if (sudo) "sudo " else "") + "test -e " + file).success
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.createFile(
|
||||||
|
fullyQualifiedFilename: String,
|
||||||
|
text: String?,
|
||||||
|
posixFilePermission: String? = null,
|
||||||
|
sudo: Boolean = false
|
||||||
|
): ProvResult =
|
||||||
|
def {
|
||||||
|
val withSudo = if (sudo) "sudo " else ""
|
||||||
|
posixFilePermission?.let {
|
||||||
|
ensureValidPosixFilePermission(posixFilePermission)
|
||||||
|
cmd(withSudo + "install -m $posixFilePermission /dev/null $fullyQualifiedFilename")
|
||||||
|
}
|
||||||
|
if (text != null) {
|
||||||
|
if (sudo) {
|
||||||
|
cmd(
|
||||||
|
"printf " + text.escapeProcentForPrintf()
|
||||||
|
.escapeAndEncloseByDoubleQuoteForShell() + " | sudo tee $fullyQualifiedFilename > /dev/null"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
cmd(
|
||||||
|
"printf " + text.escapeProcentForPrintf()
|
||||||
|
.escapeAndEncloseByDoubleQuoteForShell() + " > $fullyQualifiedFilename"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cmd(withSudo + "touch $fullyQualifiedFilename")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.createSecretFile(
|
||||||
|
fullyQualifiedFilename: String,
|
||||||
|
secret: Secret,
|
||||||
|
posixFilePermission: String? = null
|
||||||
|
): ProvResult =
|
||||||
|
def {
|
||||||
|
posixFilePermission?.let {
|
||||||
|
ensureValidPosixFilePermission(posixFilePermission)
|
||||||
|
cmd("install -m $posixFilePermission /dev/null $fullyQualifiedFilename")
|
||||||
|
}
|
||||||
|
cmdNoLog("echo '" + secret.plain().escapeSingleQuote() + "' > $fullyQualifiedFilename")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.deleteFile(file: String, sudo: Boolean = false): ProvResult = def {
|
||||||
|
cmd((if (sudo) "sudo " else "") + "rm $file")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.fileContainsText(file: String, content: String, sudo: Boolean = false): Boolean {
|
||||||
|
return cmdNoEval((if (sudo) "sudo " else "") + "grep '${content.escapeSingleQuote()}' $file").success
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.fileContent(file: String, sudo: Boolean = false): String? {
|
||||||
|
return cmd((if (sudo) "sudo " else "") + "cat $file").out
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.addTextToFile(
|
||||||
|
text: String,
|
||||||
|
file: File,
|
||||||
|
doNotAddIfExisting: Boolean = true,
|
||||||
|
sudo: Boolean = false
|
||||||
|
): ProvResult =
|
||||||
|
def {
|
||||||
|
// TODO find solution without trim handling spaces, newlines, etc correctly
|
||||||
|
val findCmd = "grep '${text.trim().escapeSingleQuote()}' ${file}"
|
||||||
|
val findResult = cmdNoEval(if (sudo) findCmd.sudoizeCommand() else findCmd)
|
||||||
|
if (!findResult.success || !doNotAddIfExisting) {
|
||||||
|
val addCmd = "printf \"" + text.escapeDoubleQuote() + "\" >> " + file
|
||||||
|
cmd(if (sudo) addCmd.sudoizeCommand() else addCmd)
|
||||||
|
} else {
|
||||||
|
ProvResult(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.replaceTextInFile(file: String, oldText: String, replacement: String) = def {
|
||||||
|
replaceTextInFile(file, Regex.fromLiteral(oldText), Regex.escapeReplacement(replacement))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.replaceTextInFile(file: String, oldText: Regex, replacement: String) = def {
|
||||||
|
// todo: only use sudo for root or if owner different from current
|
||||||
|
val content = fileContent(file, true)
|
||||||
|
if (content != null) {
|
||||||
|
cmd("sudo truncate -s 0 $file")
|
||||||
|
addTextToFile(content.replace(oldText, Regex.escapeReplacement(replacement)), File(file), sudo = true)
|
||||||
|
} else {
|
||||||
|
ProvResult(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.insertTextInFile(file: String, textBehindWhichToInsert: Regex, textToInsert: String) = def {
|
||||||
|
// todo: only use sudo for root or if owner different from current
|
||||||
|
val content = fileContent(file, true)
|
||||||
|
if (content != null) {
|
||||||
|
val match = textBehindWhichToInsert.find(content)
|
||||||
|
if (match != null) {
|
||||||
|
cmd("sudo truncate -s 0 $file")
|
||||||
|
addTextToFile(
|
||||||
|
content.replace(textBehindWhichToInsert, match.value + Regex.escapeReplacement(textToInsert)),
|
||||||
|
File(file),
|
||||||
|
sudo = true
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ProvResult(false, err = "Text not found")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ProvResult(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.dirExists(dir: String, path: String? = null, sudo: Boolean = false): Boolean {
|
||||||
|
val effectivePath = if (path != null) path else
|
||||||
|
(if (dir.startsWith(File.separator)) File.separator else "~" + File.separator)
|
||||||
|
val cmd = "cd $effectivePath && test -d $dir"
|
||||||
|
return cmdNoEval(if (sudo) cmd.sudoizeCommand() else cmd).success
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.createDir(
|
||||||
|
dir: String,
|
||||||
|
path: String = "~/",
|
||||||
|
failIfExisting: Boolean = false,
|
||||||
|
sudo: Boolean = false
|
||||||
|
): ProvResult = def {
|
||||||
|
if (!failIfExisting && dirExists(dir, path, sudo)) {
|
||||||
|
ProvResult(true)
|
||||||
|
} else {
|
||||||
|
val cmd = "cd $path && mkdir $dir"
|
||||||
|
cmd(if (sudo) cmd.sudoizeCommand() else cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.createDirs(
|
||||||
|
dirs: String,
|
||||||
|
path: String = "~/",
|
||||||
|
failIfExisting: Boolean = false,
|
||||||
|
sudo: Boolean = false
|
||||||
|
): ProvResult = def {
|
||||||
|
if (!failIfExisting && dirExists(dirs, path, sudo)) {
|
||||||
|
ProvResult(true)
|
||||||
|
} else {
|
||||||
|
val cmd = "cd $path && mkdir -p $dirs"
|
||||||
|
cmd(if (sudo) cmd.sudoizeCommand() else cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.deleteDir(dir: String, path: String, sudo: Boolean = false): ProvResult {
|
||||||
|
if ("" == path)
|
||||||
|
throw RuntimeException("In deleteDir: path must not be empty.")
|
||||||
|
val cmd = "cd $path && rmdir $dir"
|
||||||
|
return if (!sudo) {
|
||||||
|
cmd(cmd)
|
||||||
|
} else {
|
||||||
|
cmd(cmd.sudoizeCommand())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.userHome(): String {
|
||||||
|
val user = cmd("whoami").out
|
||||||
|
if (user == null) {
|
||||||
|
throw RuntimeException("Could not determine user with whoami")
|
||||||
|
} else {
|
||||||
|
// assume default home folder
|
||||||
|
return "/home/" + user.trim() + "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun ensureValidPosixFilePermission(posixFilePermission: String) {
|
||||||
|
if (!Regex("^[0-7]{3}$").matches(posixFilePermission)) throw RuntimeException("Wrong file permission ($posixFilePermission), permission must consist of 3 digits as e.g. 664 ")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun String.sudoizeCommand(): String {
|
||||||
|
return "sudo " + SHELL + " -c " + this.escapeAndEncloseByDoubleQuoteForShell()
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.git
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.provisionGit(
|
||||||
|
userName: String? = null,
|
||||||
|
email: String? = null,
|
||||||
|
signingKey: String? = null,
|
||||||
|
diffTool: String? = null
|
||||||
|
): ProvResult = def {
|
||||||
|
|
||||||
|
aptInstall("git")
|
||||||
|
|
||||||
|
cmd("git config --global push.default simple")
|
||||||
|
userName?.let { cmd("git config --global user.name $it") }
|
||||||
|
email?.let { cmd("git config --global user.email $it") }
|
||||||
|
signingKey?.let { cmd("git config --global user.signingkey $it") }
|
||||||
|
diffTool?.let { cmd("git config --global --add diff.tool $it") } ?: ProvResult(true)
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.git.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.addTextToFile
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createDir
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.dirExists
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.base.isHostKnown
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.utils.printToShell
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param host name or ip
|
||||||
|
* @param rsaFingerprints
|
||||||
|
*/
|
||||||
|
private fun Prov.trustHost(host: String, rsaFingerprints: Set<String>) = def {
|
||||||
|
if (!isHostKnown(host)) {
|
||||||
|
// logic based on https://serverfault.com/questions/447028/non-interactive-git-clone-ssh-fingerprint-prompt
|
||||||
|
val key = cmd("ssh-keyscan $host").out
|
||||||
|
if (key == null) {
|
||||||
|
ProvResult(false, "No key retrieved for $host")
|
||||||
|
} else {
|
||||||
|
val c = printToShell(key).trim()
|
||||||
|
val fpr = cmd(c + " | ssh-keygen -lf -").out
|
||||||
|
if (rsaFingerprints.contains(fpr)
|
||||||
|
) {
|
||||||
|
createDir(".ssh", "~/")
|
||||||
|
cmd(printToShell(key) + " >> ~/.ssh/known_hosts")
|
||||||
|
} else {
|
||||||
|
ProvResult(false, "Fingerprint $fpr not valid for $host")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ProvResult(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.gitClone(repo: String, path: String, pullIfExisting: Boolean = true): ProvResult = def {
|
||||||
|
val dir = cmdNoEval("basename $repo .git").out?.trim()
|
||||||
|
if (dir == null) {
|
||||||
|
ProvResult(false, err = "$repo is not a valid git repository")
|
||||||
|
} else {
|
||||||
|
val pathToDir = if (path.endsWith("/")) path + dir else path + "/" + dir
|
||||||
|
if (dirExists(pathToDir + "/.git/")) {
|
||||||
|
if (pullIfExisting) {
|
||||||
|
cmd("cd $pathToDir && git pull")
|
||||||
|
} else {
|
||||||
|
ProvResult(true, out = "Repo $repo is already existing")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cmd("cd $path && git clone $repo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.trustGithub() = def {
|
||||||
|
// current see https://docs.github.com/en/github/authenticating-to-github/githubs-ssh-key-fingerprints
|
||||||
|
val fingerprints = setOf(
|
||||||
|
"2048 SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com (RSA)\n",
|
||||||
|
"2048 SHA256:br9IjFspm1vxR3iA35FWE+4VTyz1hYVLIE2t1/CeyWQ github.com (RSA)\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
trustHost("github.com", fingerprints)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.trustGitlab() = def {
|
||||||
|
// from https://docs.gitlab.com/ee/user/gitlab_com/
|
||||||
|
val gitlabFingerprints = """
|
||||||
|
gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf
|
||||||
|
gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9
|
||||||
|
gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=
|
||||||
|
""".trimIndent()
|
||||||
|
addTextToFile("\n" + gitlabFingerprints+ "\n", File("~/.ssh/known_hosts"))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.install.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
|
||||||
|
|
||||||
|
private var aptInit = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs package(s) by using package manager "apt".
|
||||||
|
*
|
||||||
|
* @param packages the packages to be installed, packages separated by space if there are more than one
|
||||||
|
*/
|
||||||
|
fun Prov.aptInstall(packages: String): ProvResult = def {
|
||||||
|
if (!aptInit) {
|
||||||
|
cmd("sudo apt-get update")
|
||||||
|
cmd("sudo apt-get install -qy apt-utils")
|
||||||
|
aptInit = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val packageList = packages.split(" ")
|
||||||
|
for (packg in packageList) {
|
||||||
|
// see https://superuser.com/questions/164553/automatically-answer-yes-when-using-apt-get-install
|
||||||
|
cmd("sudo DEBIAN_FRONTEND=noninteractive apt-get install -qy $packg")
|
||||||
|
}
|
||||||
|
ProvResult(true) // dummy
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs a package from a ppa (personal package archive) by using package manager "apt".
|
||||||
|
*
|
||||||
|
* @param packageName the package to install
|
||||||
|
*/
|
||||||
|
fun Prov.aptInstallFromPpa(launchPadUser: String, ppaName: String, packageName: String): ProvResult = def {
|
||||||
|
aptInstall("software-properties-common") // for being able to use add-apt-repository
|
||||||
|
cmd("sudo add-apt-repository -y ppa:$launchPadUser/$ppaName")
|
||||||
|
aptInstall(packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a program is installed
|
||||||
|
*
|
||||||
|
* @param packageName to check
|
||||||
|
* @return true if program is installed
|
||||||
|
*/
|
||||||
|
@Suppress("unused") // used externally
|
||||||
|
fun Prov.isPackageInstalled(packageName: String): Boolean {
|
||||||
|
return chk("timeout 2 dpkg -l $packageName")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a package including its configuration and data files
|
||||||
|
*/
|
||||||
|
@Suppress("unused") // used externally
|
||||||
|
fun Prov.aptPurge(packageName: String): Boolean {
|
||||||
|
return chk("sudo apt-get purge -qy $packageName")
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.keys
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.base.configureGpgKeys
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.base.configureSshKeys
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.secret.SecretSourceType
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
|
open class KeyPair(val publicKey: Secret, val privateKey: Secret)
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class KeyPairSource(val sourceType: SecretSourceType, val publicKey: String, val privateKey: String) {
|
||||||
|
fun keyPair() : KeyPair {
|
||||||
|
val pub = sourceType.secret(publicKey)
|
||||||
|
val priv = sourceType.secret(privateKey)
|
||||||
|
return KeyPair(pub, priv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* provisions gpg and/or ssh keys for the current user
|
||||||
|
*/
|
||||||
|
fun Prov.provisionKeysCurrentUser(gpgKeys: KeyPair? = null, sshKeys: KeyPair? = null) = requireAll {
|
||||||
|
gpgKeys?.let { configureGpgKeys(it, true) }
|
||||||
|
sshKeys?.let { configureSshKeys(it) }
|
||||||
|
ProvResult(true) // dummy
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.keys.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createDir
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createFile
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createSecretFile
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.dirExists
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.KeyPair
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.utils.printToShell
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs a gpg key pair for the current user.
|
||||||
|
*
|
||||||
|
* @param gpgKeys
|
||||||
|
* @param trust whether to trust keys with trust-level 5 (ultimate)
|
||||||
|
*/
|
||||||
|
fun Prov.configureGpgKeys(gpgKeys: KeyPair, trust: Boolean = false, skipIfExistin: Boolean = true) = requireAll {
|
||||||
|
aptInstall("gnupg")
|
||||||
|
val fingerprint = gpgFingerprint(gpgKeys.publicKey.plain())
|
||||||
|
if (fingerprint == null) {
|
||||||
|
ProvResult(false, err = "Fingerprint of key could not be determined")
|
||||||
|
} else {
|
||||||
|
if (gpgKeysInstalled(fingerprint) && skipIfExistin) {
|
||||||
|
ProvResult(true, out = "Keys were already installed")
|
||||||
|
} else {
|
||||||
|
val pubkeyFile = "~/pub-key.asc"
|
||||||
|
val privkeyFile = "~/priv-key.asc"
|
||||||
|
|
||||||
|
createSecretFile(pubkeyFile, gpgKeys.publicKey)
|
||||||
|
createSecretFile(privkeyFile, gpgKeys.privateKey)
|
||||||
|
|
||||||
|
cmd("gpg --import $pubkeyFile")
|
||||||
|
|
||||||
|
// using option --batch for older keys; see https://superuser.com/questions/1135812/gpg2-asking-for-passphrase-when-importing-secret-keys
|
||||||
|
cmd("gpg --batch --import $privkeyFile")
|
||||||
|
|
||||||
|
if (trust) {
|
||||||
|
cmd("printf \"5\\ny\\n\" | gpg --no-tty --command-fd 0 --expert --edit-key $fingerprint trust")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd("shred $pubkeyFile")
|
||||||
|
cmd("shred $privkeyFile")
|
||||||
|
|
||||||
|
configureGPGAgent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun Prov.configureGPGAgent() = def {
|
||||||
|
if (dirExists(".gnupg")) {
|
||||||
|
createDir(".gnupg", "~/")
|
||||||
|
}
|
||||||
|
val content = """
|
||||||
|
allow-preset-passphrase
|
||||||
|
allow-loopback-pinentry
|
||||||
|
""".trimIndent()
|
||||||
|
createFile("~/.gnupg/gpg-agent.conf", content)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun Prov.gpgKeysInstalled(fingerprint: String): Boolean {
|
||||||
|
return cmdNoLog("gpg --list-keys $fingerprint").success
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.gpgFingerprint(pubKey: String): String? {
|
||||||
|
val result =
|
||||||
|
cmdNoLog(" " + printToShell(pubKey) + " | gpg --with-colons --import-options show-only --import --fingerprint")
|
||||||
|
return result.out?.let { """^fpr:*([A-Z0-9]*):$""".toRegex(RegexOption.MULTILINE).find(it)?.groupValues?.get(1) }
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.keys.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createDir
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createSecretFile
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.KeyPair
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* installs ssh keys for active user
|
||||||
|
*/
|
||||||
|
fun Prov.configureSshKeys(sshKeys: KeyPair) = def {
|
||||||
|
createDir(".ssh", "~/")
|
||||||
|
createSecretFile("~/.ssh/id_rsa.pub", sshKeys.publicKey, "644")
|
||||||
|
createSecretFile("~/.ssh/id_rsa", sshKeys.privateKey, "600")
|
||||||
|
configureSSHClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Prov.configureSSHClient() = def {
|
||||||
|
// TODO("Not yet implemented")
|
||||||
|
ProvResult(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies a host or Ip to be trusted
|
||||||
|
*
|
||||||
|
* ATTENTION:
|
||||||
|
* This method is NOT secure as a man-in-the-middle could compromise the connection.
|
||||||
|
* Don't use this for critical systems resp. environments
|
||||||
|
*/
|
||||||
|
fun Prov.trustServer(hostOrIp: String) = def {
|
||||||
|
cmd("ssh-keyscan $hostOrIp >> ~/.ssh/known_hosts")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the specified hostname or Ip is in a known_hosts file
|
||||||
|
*
|
||||||
|
* @return whether if was found
|
||||||
|
*/
|
||||||
|
fun Prov.isHostKnown(hostOrIp: String) : Boolean {
|
||||||
|
return cmdNoEval("ssh-keygen -F $hostOrIp").out?.isNotEmpty() ?: false
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.secret
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.secret.secretSources.*
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
abstract class SecretSource(protected val input: String) {
|
||||||
|
abstract fun secret() : Secret
|
||||||
|
abstract fun secretNullable() : Secret?
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class SecretSourceType() {
|
||||||
|
|
||||||
|
PLAIN, FILE, PROMPT, PASS, GOPASS;
|
||||||
|
|
||||||
|
fun secret(input: String) : Secret {
|
||||||
|
return when (this) {
|
||||||
|
PLAIN -> PlainSecretSource(input).secret()
|
||||||
|
FILE -> FileSecretSource(input).secret()
|
||||||
|
PROMPT -> PromptSecretSource().secret()
|
||||||
|
PASS -> PassSecretSource(input).secret()
|
||||||
|
GOPASS -> GopassSecretSource(input).secret()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@Suppress("unused") // for use in other projects
|
||||||
|
class SecretSupplier(private val source: SecretSourceType, val parameter: String) {
|
||||||
|
fun secret(): Secret {
|
||||||
|
return source.secret(parameter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.secret.secretSources
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.secret.SecretSource
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve secret from a file
|
||||||
|
*/
|
||||||
|
class FileSecretSource(fqFileName: String) : SecretSource(fqFileName) {
|
||||||
|
|
||||||
|
override fun secret(): Secret {
|
||||||
|
val p = Prov.newInstance(name = "FileSecretSource")
|
||||||
|
return p.getSecret("cat " + input) ?: throw Exception("Failed to get secret.")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun secretNullable(): Secret? {
|
||||||
|
val p = Prov.newInstance(name = "FileSecretSource")
|
||||||
|
return p.getSecret("cat " + input)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.secret.secretSources
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.secret.SecretSource
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve secret from gopass
|
||||||
|
*/
|
||||||
|
class GopassSecretSource(path: String) : SecretSource(path) {
|
||||||
|
override fun secret(): Secret {
|
||||||
|
return secretNullable() ?: throw Exception("Failed to get \"$input\" secret from gopass.")
|
||||||
|
}
|
||||||
|
override fun secretNullable(): Secret? {
|
||||||
|
val p = Prov.newInstance(name = "GopassSecretSource for $input")
|
||||||
|
return p.getSecret("gopass show -f " + input)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.secret.secretSources
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.secret.SecretSource
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve secret from passwordstore on Unix
|
||||||
|
*/
|
||||||
|
class PassSecretSource(path: String) : SecretSource(path) {
|
||||||
|
override fun secret(): Secret {
|
||||||
|
val p = Prov.newInstance(name = "PassSecretSource")
|
||||||
|
return p.getSecret("pass " + input) ?: throw Exception("Failed to get secret.")
|
||||||
|
}
|
||||||
|
override fun secretNullable(): Secret? {
|
||||||
|
val p = Prov.newInstance(name = "PassSecretSource")
|
||||||
|
return p.getSecret("pass " + input)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.secret.secretSources
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.secret.SecretSource
|
||||||
|
|
||||||
|
|
||||||
|
class PlainSecretSource(plainSecret: String) : SecretSource(plainSecret) {
|
||||||
|
override fun secret(): Secret {
|
||||||
|
return Secret(input)
|
||||||
|
}
|
||||||
|
override fun secretNullable(): Secret {
|
||||||
|
return Secret(input)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.secret.secretSources
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.secret.SecretSource
|
||||||
|
import java.awt.FlowLayout
|
||||||
|
import javax.swing.*
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordPanel : JPanel(FlowLayout()) {
|
||||||
|
|
||||||
|
private val passwordField = JPasswordField(20)
|
||||||
|
private var entered = false
|
||||||
|
|
||||||
|
val enteredPassword
|
||||||
|
get() = if (entered) String(passwordField.password) else null
|
||||||
|
|
||||||
|
init {
|
||||||
|
add(JLabel("Password: "))
|
||||||
|
add(passwordField)
|
||||||
|
passwordField.setActionCommand("OK")
|
||||||
|
passwordField.addActionListener {
|
||||||
|
if (it.actionCommand == "OK") {
|
||||||
|
entered = true
|
||||||
|
|
||||||
|
SwingUtilities.getWindowAncestor(it.source as JComponent)
|
||||||
|
.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun request(passwordIdentifier: String) = apply {
|
||||||
|
JOptionPane.showOptionDialog(null, this@PasswordPanel,
|
||||||
|
"Enter $passwordIdentifier",
|
||||||
|
JOptionPane.DEFAULT_OPTION,
|
||||||
|
JOptionPane.INFORMATION_MESSAGE,
|
||||||
|
null, emptyArray(), null)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun requestPassword(passwordIdentifier: String) = PasswordPanel()
|
||||||
|
.request(passwordIdentifier)
|
||||||
|
.enteredPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PromptSecretSource(text: String = "Secret/Password") : SecretSource(text) {
|
||||||
|
|
||||||
|
override fun secret(): Secret {
|
||||||
|
val password = PasswordPanel.requestPassword(input)
|
||||||
|
if (password == null) {
|
||||||
|
throw IllegalArgumentException("Failed to retrieve secret from prompting.")
|
||||||
|
} else {
|
||||||
|
return Secret(password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun secretNullable(): Secret? {
|
||||||
|
val password = PasswordPanel.requestPassword(input)
|
||||||
|
|
||||||
|
return if(password == null) {
|
||||||
|
null
|
||||||
|
}else {
|
||||||
|
Secret(password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.user
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.KeyPairSource
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.FileReader
|
||||||
|
import java.io.FileWriter
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class UserConfig(val userName: String, val gitEmail: String? = null, val gpg: KeyPairSource? = null, val ssh: KeyPairSource? = null)
|
||||||
|
|
||||||
|
|
||||||
|
// -------------------------------------------- file methods ------------------------------------
|
||||||
|
@Suppress("unused")
|
||||||
|
fun readUserConfigFromFile(filename: String = "UserConfig.json") : UserConfig {
|
||||||
|
// read from file
|
||||||
|
val inputAsString = BufferedReader(FileReader(filename)).use { it.readText() }
|
||||||
|
|
||||||
|
// serializing objects
|
||||||
|
return Json.decodeFromString(UserConfig.serializer(), inputAsString)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun writeUserConfigToFile(config: UserConfig) {
|
||||||
|
val fileName = "UserConfig.json"
|
||||||
|
|
||||||
|
FileWriter(fileName).use { it.write(Json.encodeToString(UserConfig.serializer(), config)) }
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.user.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.RemoteProcessor
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createDirs
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.fileExists
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.git.provisionGit
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.base.gpgFingerprint
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.provisionKeysCurrentUser
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.user.UserConfig
|
||||||
|
import java.net.InetAddress
|
||||||
|
|
||||||
|
|
||||||
|
fun Prov.userExists(userName: String): Boolean {
|
||||||
|
return cmdNoEval("grep -c '^$userName:' /etc/passwd").success
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new user.
|
||||||
|
*/
|
||||||
|
fun Prov.createUser(
|
||||||
|
userName: String,
|
||||||
|
password: Secret? = null,
|
||||||
|
sudo: Boolean = false,
|
||||||
|
copyAuthorizedKeysFromCurrentUser: Boolean = false
|
||||||
|
): ProvResult = requireAll {
|
||||||
|
if (!userExists(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)
|
||||||
|
if (sudo) {
|
||||||
|
makeUserSudoerWithNoSudoPasswordRequired(userName)
|
||||||
|
}
|
||||||
|
val authorizedKeysFile = "~/.ssh/authorized_keys"
|
||||||
|
if (copyAuthorizedKeysFromCurrentUser && fileExists(authorizedKeysFile)) {
|
||||||
|
createDirs("/home/$userName/.ssh")
|
||||||
|
val newAuthorizedKeysFile = "/home/$userName/.ssh/authorized_keys"
|
||||||
|
cmd("sudo cp $authorizedKeysFile $newAuthorizedKeysFile")
|
||||||
|
cmd("chown $userName $newAuthorizedKeysFile")
|
||||||
|
|
||||||
|
}
|
||||||
|
ProvResult(true) // dummy
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures gpg and ssh keys for the current if keys are provided in the config.
|
||||||
|
* Installs and configures git for the user if gitEmail is provided in the config.
|
||||||
|
* Does NOT CREATE the user.
|
||||||
|
*/
|
||||||
|
fun Prov.configureUser(config: UserConfig) = requireAll {
|
||||||
|
provisionKeysCurrentUser(
|
||||||
|
config.gpg?.keyPair(),
|
||||||
|
config.ssh?.keyPair()
|
||||||
|
)
|
||||||
|
|
||||||
|
config.gitEmail?.run {
|
||||||
|
provisionGit(
|
||||||
|
config.userName,
|
||||||
|
config.gitEmail,
|
||||||
|
config.gpg?.keyPair()?.let { gpgFingerprint(it.publicKey.plain()) })
|
||||||
|
} ?: ProvResult(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
// todo create test
|
||||||
|
fun Prov.deleteUser(userName: String, deleteHomeDir: Boolean = false): ProvResult = requireAll {
|
||||||
|
val flagToDeleteHomeDir = if (deleteHomeDir) " -r " else ""
|
||||||
|
if (userExists(userName)) {
|
||||||
|
cmd("sudo userdel $flagToDeleteHomeDir $userName")
|
||||||
|
} else {
|
||||||
|
ProvResult(false, err = "User $userName cannot be deleted as it does not exist.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes userName a sudoer who does not need a password to sudo.
|
||||||
|
* The current (executing) user must already be a sudoer. If he is a sudoer with password required then
|
||||||
|
* his password must be provided.
|
||||||
|
*/
|
||||||
|
fun Prov.makeUserSudoerWithNoSudoPasswordRequired(
|
||||||
|
userName: String,
|
||||||
|
password: Secret? = null,
|
||||||
|
overwriteFile: Boolean = false
|
||||||
|
): ProvResult = def {
|
||||||
|
val userSudoFile = "/etc/sudoers.d/$userName"
|
||||||
|
if (!fileExists(userSudoFile) || overwriteFile) {
|
||||||
|
val sudoPrefix = if (password == null) "sudo" else "echo ${password.plain()} | sudo -S"
|
||||||
|
// see https://stackoverflow.com/questions/323957/how-do-i-edit-etc-sudoers-from-a-script
|
||||||
|
val result = cmdNoLog(sudoPrefix + " sh -c \"echo '$userName ALL=(ALL) NOPASSWD:ALL' | (sudo su -c 'EDITOR=\"tee\" visudo -f " + userSudoFile + "')\"")
|
||||||
|
// don't log the command (containing the password) resp. don't include it in the ProvResult, just include success and err
|
||||||
|
ProvResult(result.success, err = result.err)
|
||||||
|
} else {
|
||||||
|
ProvResult(true, out = "File already exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the current (executing) user be able to sudo without password.
|
||||||
|
* IMPORTANT: Current user must already by sudoer when calling this function.
|
||||||
|
*/
|
||||||
|
@Suppress("unused") // used externally
|
||||||
|
fun Prov.makeUserSudoerWithNoSudoPasswordRequired(password: Secret) = def {
|
||||||
|
val currentUser = whoami()
|
||||||
|
if (currentUser != null) {
|
||||||
|
makeUserSudoerWithNoSudoPasswordRequired(currentUser, password, overwriteFile = true)
|
||||||
|
} else {
|
||||||
|
ProvResult(false, "Current user could not be determined.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if user is in group sudo.
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
fun Prov.userIsInGroupSudo(userName: String): Boolean {
|
||||||
|
return cmd("getent group sudo | grep -c '$userName'").success
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if current user can execute sudo commands.
|
||||||
|
*/
|
||||||
|
@Suppress("unused")
|
||||||
|
fun Prov.currentUserCanSudo(): Boolean {
|
||||||
|
return cmd("timeout 1 sudo id").success
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns username of current user if it can be determined
|
||||||
|
*/
|
||||||
|
fun Prov.whoami(): String? {
|
||||||
|
return cmd("whoami").run { if (success) out?.trim() else null }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new user on the specified host.
|
||||||
|
*
|
||||||
|
* @host hostname or ip-address
|
||||||
|
* @hostUser user on the remote system, which is used to create the new user,
|
||||||
|
* hostUser must be sudoer
|
||||||
|
* @hostPassword pw of hostUser on the remote system;
|
||||||
|
* ssh-key authentication will be used if hostPassword is null
|
||||||
|
*/
|
||||||
|
@Suppress("api") // use externally
|
||||||
|
fun createRemoteUser(
|
||||||
|
host: InetAddress,
|
||||||
|
hostUser: String,
|
||||||
|
hostPassword: Secret?,
|
||||||
|
newUserName: String,
|
||||||
|
newUserPW: Secret,
|
||||||
|
makeNewUserSudoer: Boolean = false
|
||||||
|
) {
|
||||||
|
Prov.newInstance(RemoteProcessor(host, hostUser, hostPassword), name = "createRemoteUser")
|
||||||
|
.createUser(newUserName, newUserPW, makeNewUserSudoer)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.utils
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.escapeBackslash
|
||||||
|
import org.domaindrivenarchitecture.provs.core.escapeDoubleQuote
|
||||||
|
|
||||||
|
|
||||||
|
// todo: investigate to use .escapeAndEncloseByDoubleQuoteForShell() or similar instead (?)
|
||||||
|
internal fun printToShell(text: String): String {
|
||||||
|
return "echo -n \"${text.escapeBackslash().escapeDoubleQuote()}\""
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.web.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.isPackageInstalled
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads a file from the given URL using curl
|
||||||
|
*
|
||||||
|
* @param path where to download to
|
||||||
|
* @param url file to download
|
||||||
|
* @param filename filename after download
|
||||||
|
*/
|
||||||
|
@Suppress("unused") // used externally
|
||||||
|
fun Prov.downloadFromURL(url: String, filename: String? = null, path: String? = null, sudo: Boolean = false) : ProvResult = def {
|
||||||
|
|
||||||
|
if (!isPackageInstalled("curl")) aptInstall("curl")
|
||||||
|
|
||||||
|
if (filename == null) {
|
||||||
|
cmd("curl $url", path, sudo)
|
||||||
|
} else {
|
||||||
|
cmd("curl $url -o $filename", path, sudo)
|
||||||
|
}
|
||||||
|
}
|
7
bin/test/WorkplaceConfigExample.json
Normal file
7
bin/test/WorkplaceConfigExample.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"type": "MINIMAL",
|
||||||
|
"ssh": null,
|
||||||
|
"gpg": null,
|
||||||
|
"gitUserName": "mygitusername",
|
||||||
|
"gitEmail": "my@git.email"
|
||||||
|
}
|
478
bin/test/org/domaindrivenarchitecture/provs/core/ProvTest.kt
Normal file
478
bin/test/org/domaindrivenarchitecture/provs/core/ProvTest.kt
Normal file
|
@ -0,0 +1,478 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.provideContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.NonCi
|
||||||
|
import org.domaindrivenarchitecture.provs.test.testLocal
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import org.junit.jupiter.api.Disabled
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||||
|
import org.junit.jupiter.api.condition.OS
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.PrintStream
|
||||||
|
|
||||||
|
|
||||||
|
internal class ProvTest {
|
||||||
|
|
||||||
|
private fun Prov.def_returnungFalse() = def {
|
||||||
|
ProvResult(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Prov.def_returningTrue() = def {
|
||||||
|
ProvResult(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
fun cmd_onLinux() {
|
||||||
|
// when
|
||||||
|
val res = Prov.newInstance(name = "testing").cmd("echo --testing--").success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
@ContainerTest
|
||||||
|
fun sh_onLinux() {
|
||||||
|
// given
|
||||||
|
val script = """
|
||||||
|
# test some script commands
|
||||||
|
|
||||||
|
ping -c1 nu.nl
|
||||||
|
echo something
|
||||||
|
ping -c1 github.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = Prov.newInstance(name = "testing").sh(script).success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
@ContainerTest
|
||||||
|
@NonCi
|
||||||
|
fun sh_onLinux_with_dir_and_sudo() {
|
||||||
|
// given
|
||||||
|
val script = """
|
||||||
|
# test some script commands
|
||||||
|
|
||||||
|
ping -c1 google.com
|
||||||
|
echo something
|
||||||
|
ping -c1 github.com
|
||||||
|
echo 1 # comment behind command
|
||||||
|
"""
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = Prov.newInstance(name = "provs_test").sh(script, "/root", true).success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.WINDOWS)
|
||||||
|
fun cmd_onWindows() {
|
||||||
|
// when
|
||||||
|
val res = Prov.newInstance(name = "testing").cmd("echo --testing--").success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.WINDOWS)
|
||||||
|
fun sh_onWindows() {
|
||||||
|
// given
|
||||||
|
val script = """
|
||||||
|
# test some script commands
|
||||||
|
|
||||||
|
ping -n 1 nu.nl
|
||||||
|
echo something
|
||||||
|
ping -n 1 github.com
|
||||||
|
"""
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = Prov.newInstance(name = "testing").sh(script).success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun def_modeOptional_result_true() {
|
||||||
|
// given
|
||||||
|
fun Prov.tst_def() = optional {
|
||||||
|
def_returnungFalse()
|
||||||
|
def_returningTrue()
|
||||||
|
def_returnungFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = testLocal().tst_def().success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun def_modeLast_result_true() {
|
||||||
|
// given
|
||||||
|
fun Prov.tst_def() = requireLast {
|
||||||
|
def_returnungFalse()
|
||||||
|
def_returningTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = testLocal().tst_def().success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun def_modeLast_result_false() {
|
||||||
|
// given
|
||||||
|
fun Prov.tst_def() = requireLast {
|
||||||
|
def_returningTrue()
|
||||||
|
def_returnungFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = testLocal().tst_def().success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(!res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun def_mode_ALL_result_true() {
|
||||||
|
// given
|
||||||
|
fun Prov.tst_def_all_true_mode_ALL() = requireAll {
|
||||||
|
def_returningTrue()
|
||||||
|
def_returningTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = testLocal().tst_def_all_true_mode_ALL().success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// given
|
||||||
|
fun Prov.tst_def_one_false_mode_ALL() = requireAll {
|
||||||
|
def_returningTrue()
|
||||||
|
def_returnungFalse()
|
||||||
|
def_returningTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun def_modeALL_resultFalse() {
|
||||||
|
// when
|
||||||
|
val res = testLocal().tst_def_one_false_mode_ALL().success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(!res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// given
|
||||||
|
fun Prov.tst_def_one_false_mode_ALL_nested() = requireAll {
|
||||||
|
def_returningTrue()
|
||||||
|
tst_def_one_false_mode_ALL()
|
||||||
|
def_returningTrue()
|
||||||
|
tst_ALL_returningTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// given
|
||||||
|
fun Prov.tst_ALL_returningTrue() = requireAll {
|
||||||
|
ProvResult(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun def_modeALLnested_resultFalse() {
|
||||||
|
// when
|
||||||
|
val res = testLocal().tst_def_one_false_mode_ALL_nested().success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(!res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun def_mode_ALL_LAST_NONE_nested() {
|
||||||
|
// given
|
||||||
|
fun Prov.tst_def_last() = def {
|
||||||
|
def_returningTrue()
|
||||||
|
def_returnungFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Prov.tst_def_one_false_mode_ALL() = requireAll {
|
||||||
|
tst_def_last()
|
||||||
|
def_returningTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = testLocal().tst_def_one_false_mode_ALL().success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(!res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun def_mode_FAILEXIT_nested_false() {
|
||||||
|
// given
|
||||||
|
fun Prov.tst_def_failexit_inner() = exitOnFailure {
|
||||||
|
def_returningTrue()
|
||||||
|
def_returnungFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Prov.tst_def_failexit_outer() = exitOnFailure {
|
||||||
|
tst_def_failexit_inner()
|
||||||
|
def_returningTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = testLocal().tst_def_failexit_outer().success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(!res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun def_mode_FAILEXIT_nested_true() {
|
||||||
|
// given
|
||||||
|
fun Prov.tst_def_failexit_inner() = exitOnFailure {
|
||||||
|
def_returningTrue()
|
||||||
|
def_returningTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Prov.tst_def_failexit_outer() = exitOnFailure {
|
||||||
|
tst_def_failexit_inner()
|
||||||
|
def_returningTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = testLocal().tst_def_failexit_outer().success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun def_mode_multiple_nested() {
|
||||||
|
// given
|
||||||
|
fun Prov.tst_nested() = def {
|
||||||
|
requireAll {
|
||||||
|
def_returningTrue()
|
||||||
|
def {
|
||||||
|
def_returnungFalse()
|
||||||
|
def_returningTrue()
|
||||||
|
}
|
||||||
|
def_returnungFalse()
|
||||||
|
def_returningTrue()
|
||||||
|
optional {
|
||||||
|
def_returnungFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = testLocal().tst_nested().success
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(!res)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// given
|
||||||
|
fun Prov.checkPrereq_evaluateToFailure() = requireLast {
|
||||||
|
ProvResult(false, err = "This is a test error.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Prov.methodThatProvidesSomeOutput() = requireLast {
|
||||||
|
|
||||||
|
if (!checkPrereq_evaluateToFailure().success) {
|
||||||
|
sh(
|
||||||
|
"""
|
||||||
|
echo -Start test-
|
||||||
|
echo Some output
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
sh("echo -End test-")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun runProv_printsCorrectOutput() {
|
||||||
|
|
||||||
|
// given
|
||||||
|
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", progressType = ProgressType.NONE).methodThatProvidesSomeOutput()
|
||||||
|
|
||||||
|
// then
|
||||||
|
System.setOut(originalOut)
|
||||||
|
System.setErr(originalErr)
|
||||||
|
|
||||||
|
println(outContent.toString())
|
||||||
|
|
||||||
|
val expectedOutput = if (OS.WINDOWS.isCurrentOs) "\n" +
|
||||||
|
"============================================== SUMMARY (test Instance) ============================================== \n" +
|
||||||
|
"> Success -- methodThatProvidesSomeOutput (requireLast) \n" +
|
||||||
|
"---> FAILED -- checkPrereq_evaluateToFailure (requireLast) -- Error: This is a test error.\n" +
|
||||||
|
"---> Success -- sh \n" +
|
||||||
|
"------> Success -- cmd [cmd.exe, /c, echo -Start test-]\n" +
|
||||||
|
"------> Success -- cmd [cmd.exe, /c, echo Some output]\n" +
|
||||||
|
"---> Success -- sh \n" +
|
||||||
|
"------> Success -- cmd [cmd.exe, /c, echo -End test-]\n" +
|
||||||
|
"============================================ SUMMARY END ============================================ \n"
|
||||||
|
else if (OS.LINUX.isCurrentOs()) {
|
||||||
|
"============================================== SUMMARY (test instance) ============================================== \n" +
|
||||||
|
"> \u001B[92mSuccess\u001B[0m -- methodThatProvidesSomeOutput (requireLast) \n" +
|
||||||
|
"---> \u001B[91mFAILED\u001B[0m -- checkPrereq_evaluateToFailure (requireLast) -- Error: This is a test error.\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 Some output]\n" +
|
||||||
|
"---> \u001B[92mSuccess\u001B[0m -- sh \n" +
|
||||||
|
"------> \u001B[92mSuccess\u001B[0m -- cmd [/bin/bash, -c, echo -End test-]\n" +
|
||||||
|
"----------------------------------------------------------------------------------------------------- \n" +
|
||||||
|
"Overall > \u001B[92mSuccess\u001B[0m\n" +
|
||||||
|
"============================================ SUMMARY END ============================================ \n" +
|
||||||
|
"\n"
|
||||||
|
} else {
|
||||||
|
"OS " + System.getProperty("os.name") + " not yet supported"
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(expectedOutput, outContent.toString().replace("\r", ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun check_returnsTrue() {
|
||||||
|
// when
|
||||||
|
val res = testLocal().chk("echo 123")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun check_returnsFalse() {
|
||||||
|
// when
|
||||||
|
val res = testLocal().chk("cmddoesnotexist")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertFalse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getSecret_returnsSecret() {
|
||||||
|
// when
|
||||||
|
val res = testLocal().getSecret("echo 123")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals("123", res?.plain()?.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addResultToEval_success() {
|
||||||
|
// given
|
||||||
|
fun Prov.inner() {
|
||||||
|
addResultToEval(ProvResult(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Prov.outer() = requireAll {
|
||||||
|
inner()
|
||||||
|
ProvResult(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = testLocal().outer()
|
||||||
|
|
||||||
|
//then
|
||||||
|
assertEquals(ProvResult(true), res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addResultToEval_failure() {
|
||||||
|
// given
|
||||||
|
fun Prov.inner() {
|
||||||
|
addResultToEval(ProvResult(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Prov.outer() = requireAll {
|
||||||
|
inner()
|
||||||
|
ProvResult(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = testLocal().outer()
|
||||||
|
|
||||||
|
//then
|
||||||
|
assertEquals(ProvResult(false), res)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
@NonCi
|
||||||
|
fun inContainer_locally() {
|
||||||
|
// given
|
||||||
|
val containerName = "provs_test"
|
||||||
|
testLocal().provideContainer(containerName, "ubuntu")
|
||||||
|
|
||||||
|
fun Prov.inner() = def {
|
||||||
|
cmd("echo in container")
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
fun Prov.outer() = def {
|
||||||
|
inContainer(containerName) {
|
||||||
|
inner()
|
||||||
|
cmd("echo testfile > testfile.txt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val res = testLocal().def { outer() }
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(true, res.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
@Disabled // run manually after updating host and remoteUser
|
||||||
|
fun inContainer_remotely() {
|
||||||
|
// given
|
||||||
|
val host = "192.168.56.135"
|
||||||
|
val remoteUser = "az"
|
||||||
|
|
||||||
|
fun Prov.inner() = def {
|
||||||
|
cmd("echo 'in testfile' > testfile.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
val res = remote(host, remoteUser).def {
|
||||||
|
inner() // executed on the remote host
|
||||||
|
inContainer("prov_default") {
|
||||||
|
inner() // executed in the container on the remote host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(true, res.success)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import org.junit.jupiter.api.Disabled
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.net.UnknownHostException
|
||||||
|
|
||||||
|
internal class UtilsKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_getCallingMethodName() {
|
||||||
|
// when
|
||||||
|
val s = getCallingMethodName()
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals("test_getCallingMethodName", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ContainerTest
|
||||||
|
fun runCmdInContainer() {
|
||||||
|
// when
|
||||||
|
val res = defaultTestContainer().cmd("echo something")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun remote_emptyHost() {
|
||||||
|
assertThrows(IllegalArgumentException::class.java,
|
||||||
|
{ remote("", "user") })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun remote_invalidHost() {
|
||||||
|
assertThrows(
|
||||||
|
UnknownHostException::class.java,
|
||||||
|
{ remote("invalid_host", "user") })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled // run manually after having updated user
|
||||||
|
fun test_remote() {
|
||||||
|
assertTrue(remote("127.0.0.1", "user").cmd("echo sth").success)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.docker.platforms
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.containerRuns
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.exitAndRmContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.runContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.NonCi
|
||||||
|
import org.domaindrivenarchitecture.provs.test.testLocal
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||||
|
import org.junit.jupiter.api.condition.OS
|
||||||
|
|
||||||
|
internal class UbuntuHostDockerKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
@NonCi
|
||||||
|
fun runAndCheckAndExitContainer() {
|
||||||
|
// when
|
||||||
|
val containerName = "testContainer"
|
||||||
|
val result = testLocal().requireAll {
|
||||||
|
runContainer(containerName)
|
||||||
|
addResultToEval(ProvResult(containerRuns(containerName)))
|
||||||
|
|
||||||
|
exitAndRmContainer(containerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(ProvResult(true), result)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.entry
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.PrintStream
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun testfun(args: Array<String>) {
|
||||||
|
println("test is fun " + args.joinToString(" "))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
println("main is fun " + args.joinToString(" "))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class EntryKtTest {
|
||||||
|
|
||||||
|
private var outContent = ByteArrayOutputStream()
|
||||||
|
private var originalOut = System.out
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun redirectSystemOutStream() {
|
||||||
|
originalOut = System.out
|
||||||
|
|
||||||
|
// given
|
||||||
|
outContent = ByteArrayOutputStream()
|
||||||
|
System.setOut(PrintStream(outContent))
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun restoreSystemOutStream() {
|
||||||
|
System.setOut(originalOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_without_method_argument() {
|
||||||
|
// when
|
||||||
|
main("org.domaindrivenarchitecture.provs.core.entry.EntryTestKt")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals("main is fun \n", outContent.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_method_main_without_args() {
|
||||||
|
// when
|
||||||
|
main("org.domaindrivenarchitecture.provs.core.entry.EntryTestKt", "main")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals("main is fun \n", outContent.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_named_method_without_args() {
|
||||||
|
// when
|
||||||
|
main("org.domaindrivenarchitecture.provs.core.entry.EntryTestKt", "testfun")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals("test is fun \n", outContent.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_method_main_with_args() {
|
||||||
|
// when
|
||||||
|
main("org.domaindrivenarchitecture.provs.core.entry.EntryTestKt", "main", "arg1", "arg2")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals("main is fun arg1 arg2\n", outContent.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_named_method_with_args() {
|
||||||
|
// when
|
||||||
|
main("org.domaindrivenarchitecture.provs.core.entry.EntryTestKt", "testfun", "arg1", "arg2")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals("test is fun arg1 arg2\n", outContent.toString())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.platformTest
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.NonCi
|
||||||
|
import org.domaindrivenarchitecture.provs.test.testLocal
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||||
|
import org.junit.jupiter.api.condition.OS
|
||||||
|
|
||||||
|
internal class UbuntuProvTests {
|
||||||
|
|
||||||
|
private fun Prov.ping(url: String) = def {
|
||||||
|
xec("ping", "-c", "4", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Prov.outerPing() = def {
|
||||||
|
ping("gitlab.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
fun that_ping_works() {
|
||||||
|
// when
|
||||||
|
val res = testLocal().outerPing()
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
fun that_cmd_works() {
|
||||||
|
// given
|
||||||
|
val a = testLocal()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res1 = a.cmd("pwd")
|
||||||
|
val dir = res1.out?.trim()
|
||||||
|
val res2 = a.cmd("echo abc", dir)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res1.success)
|
||||||
|
assert(res2.success)
|
||||||
|
assert(res2.out?.trim() == "abc")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
@NonCi
|
||||||
|
fun that_cmd_works_with_sudo() {
|
||||||
|
// given
|
||||||
|
val a = testLocal()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res1 = a.cmd("echo abc", "/root", sudo = true)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res1.success)
|
||||||
|
assert(res1.out?.trim() == "abc")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
fun that_nested_shells_work() {
|
||||||
|
// given
|
||||||
|
val a = testLocal()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res1 = a.cmd("pwd")
|
||||||
|
val dir = res1.out?.trim()
|
||||||
|
val res2 = a.cmd("echo abc", dir)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res1.success)
|
||||||
|
assert(res2.success)
|
||||||
|
assert(res2.out?.trim() == "abc")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
fun that_xec_works() {
|
||||||
|
// given
|
||||||
|
val a = testLocal()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res1 = a.xec("/usr/bin/printf", "hi")
|
||||||
|
val res2 = a.xec("/bin/ping", "-c", "2", "gitlab.com")
|
||||||
|
val res3 = a.xec("/bin/bash", "-c", "echo echoed")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res1.success)
|
||||||
|
assert(res1.out?.trim() == "hi")
|
||||||
|
assert(res2.success)
|
||||||
|
assert(res3.success)
|
||||||
|
assert(res3.out?.trim() == "echoed")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.platformTest
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.test.testLocal
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||||
|
import org.junit.jupiter.api.condition.OS
|
||||||
|
|
||||||
|
internal class WinProvTests {
|
||||||
|
|
||||||
|
private fun Prov.ping(url: String) = def {
|
||||||
|
cmd("ping $url")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Prov.outerPing() = def { ping("nu.nl") }
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.WINDOWS)
|
||||||
|
fun def_definesPing_function() {
|
||||||
|
// when
|
||||||
|
val res = testLocal().outerPing()
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.WINDOWS)
|
||||||
|
fun cmd_executesCommand() {
|
||||||
|
// given
|
||||||
|
val a = testLocal()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res1 = a.cmd("echo %cd%")
|
||||||
|
val dir = res1.out?.trim()
|
||||||
|
val res2 = a.cmd("echo abc", dir)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res1.success)
|
||||||
|
assert(res1.success)
|
||||||
|
assertEquals( "abc", res2.out?.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.processors
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.newline
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||||
|
import org.junit.jupiter.api.condition.OS
|
||||||
|
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
internal class ContainerProcessorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ContainerTest
|
||||||
|
fun cmd_works_with_echo() {
|
||||||
|
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
val text = "abc123!§$%&/#äöü"
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = prov.cmd("echo '${text}'")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res.success)
|
||||||
|
assertEquals(text + newline(), res.out)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ContainerTest
|
||||||
|
fun cmdNoLog_works_with_echo() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
val text = "abc123!§$%&/#äöü"
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = prov.cmdNoLog("echo '${text}'")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res.success)
|
||||||
|
assertEquals(text + newline(), res.out)
|
||||||
|
|
||||||
|
// todo add check that cmd was not logged
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.processors
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.platforms.SHELL
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
|
import org.domaindrivenarchitecture.provs.test.testDockerWithSudo
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||||
|
import org.junit.jupiter.api.condition.OS.LINUX
|
||||||
|
|
||||||
|
val DEFAULT_START_MODE_TEST_CONTAINER = ContainerStartMode.USE_RUNNING_ELSE_CREATE
|
||||||
|
|
||||||
|
class ContainerUbuntuHostProcessorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(LINUX)
|
||||||
|
@ContainerTest
|
||||||
|
fun test_execution() {
|
||||||
|
// given
|
||||||
|
val processor =
|
||||||
|
ContainerUbuntuHostProcessor("provs_ubuntuhost_test", "ubuntu", DEFAULT_START_MODE_TEST_CONTAINER, sudo = testDockerWithSudo)
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = processor.x(SHELL, "-c", "echo -n abc")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(0, res.exitCode)
|
||||||
|
assertEquals("abc", res.out)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.core.processors
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.escapeAndEncloseByDoubleQuoteForShell
|
||||||
|
import org.domaindrivenarchitecture.provs.core.escapeProcentForPrintf
|
||||||
|
import org.domaindrivenarchitecture.provs.core.escapeSingleQuoteForShell
|
||||||
|
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.condition.EnabledOnOs
|
||||||
|
import org.junit.jupiter.api.condition.OS
|
||||||
|
|
||||||
|
|
||||||
|
internal class LocalProcessorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
fun cmd_with_printf_on_Linux() {
|
||||||
|
// given
|
||||||
|
val prov = Prov.newInstance()
|
||||||
|
val text = "abc123!§\\\$%%&/\"\\äöü'"
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = prov.cmd("printf '${text.replace("%", "%%").escapeSingleQuoteForShell()}'")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res.success)
|
||||||
|
assert(res.out == text)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
fun cmd_with_nested_shell_and_printf_on_Linux() {
|
||||||
|
// given
|
||||||
|
val prov = Prov.newInstance()
|
||||||
|
val text = "abc123!§\\$%%&/\"\\äöü'"
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = prov.cmd("sh -c " + ("sh -c " + ("printf ${text.escapeProcentForPrintf().escapeAndEncloseByDoubleQuoteForShell()}").escapeAndEncloseByDoubleQuoteForShell()).escapeAndEncloseByDoubleQuoteForShell())
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
assertEquals(text, res.out)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.WINDOWS)
|
||||||
|
fun cmd_with_echo_on_Windows() {
|
||||||
|
// given
|
||||||
|
val prov = Prov.newInstance()
|
||||||
|
val text = "abc123!\"#"
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = prov.cmd("echo $text")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res.success)
|
||||||
|
assertEquals( text + "\r\n", res.out)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
fun cmdNoLog_linux() {
|
||||||
|
// given
|
||||||
|
val prov = Prov.newInstance()
|
||||||
|
val text = "abc123!#"
|
||||||
|
val osSpecificText = if (OS.WINDOWS.isCurrentOs) text else "'$text'"
|
||||||
|
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = prov.cmdNoLog("echo $osSpecificText")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res.success)
|
||||||
|
assertEquals( text + System.lineSeparator(), res.out)
|
||||||
|
|
||||||
|
// todo add check that cmd was not logged
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun cmd_forUnkownCommand_resultWithError() {
|
||||||
|
// given
|
||||||
|
val prov = Prov.newInstance()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = prov.cmd("iamanunknowncmd")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(!res.success)
|
||||||
|
assert(res.out.isNullOrEmpty())
|
||||||
|
assert(!res.err.isNullOrEmpty())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.firewall
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.dockerProvideImage
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.dockerimages.UbuntuPlusUser
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.exitAndRmContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.core.local
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.ContainerEndMode
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.ContainerStartMode
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.ContainerUbuntuHostProcessor
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.NonCi
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
|
||||||
|
internal class ProvisionFirewallKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@NonCi
|
||||||
|
fun configureFirewall() {
|
||||||
|
// given
|
||||||
|
val dockerImage = UbuntuPlusUser()
|
||||||
|
local().dockerProvideImage(dockerImage)
|
||||||
|
val containerName = "firewall_test"
|
||||||
|
local().exitAndRmContainer(containerName)
|
||||||
|
local().cmd("sudo docker run --cap-add=NET_ADMIN -dit --name $containerName ${dockerImage.imageName()}")
|
||||||
|
val a = Prov.newInstance(
|
||||||
|
ContainerUbuntuHostProcessor(
|
||||||
|
containerName,
|
||||||
|
dockerImage.imageName(),
|
||||||
|
ContainerStartMode.USE_RUNNING_ELSE_CREATE, // already started in previous statement
|
||||||
|
ContainerEndMode.EXIT_AND_REMOVE
|
||||||
|
))
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.requireAll {
|
||||||
|
aptInstall("iptables")
|
||||||
|
provisionFirewall()
|
||||||
|
}
|
||||||
|
local().exitAndRmContainer(containerName)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package nexus
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nexus.provisionNexusWithDocker
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Disabled
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
|
||||||
|
internal class ProvisionNexusKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled("Find out how to run docker in docker")
|
||||||
|
fun provisionNexusWithDocker() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.requireAll {
|
||||||
|
provisionNexusWithDocker()
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.NonCi
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.replaceTextInFile
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base.*
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.fileExists
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
|
||||||
|
internal class ProvisionNginxKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@NonCi
|
||||||
|
fun provisionNginxStandAlone_customConfig() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
val config = """
|
||||||
|
events {} # event context have to be defined to consider config valid
|
||||||
|
|
||||||
|
http {
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
return 200 "Hello";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
a.aptInstall("curl")
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.requireAll {
|
||||||
|
provisionNginxStandAlone(NginxConf(config))
|
||||||
|
cmd("curl localhost")
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@NonCi
|
||||||
|
fun provisionNginxStandAlone_defaultConfig() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.requireAll {
|
||||||
|
provisionNginxStandAlone()
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@NonCi
|
||||||
|
fun provisionNginxStandAlone_sslConfig() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
a.def {
|
||||||
|
val file = "/etc/ssl/openssl.cnf"
|
||||||
|
if (fileExists(file)) {
|
||||||
|
replaceTextInFile(file, "RANDFILE", "#RANDFILE")
|
||||||
|
}
|
||||||
|
aptInstall("openssl")
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.def {
|
||||||
|
nginxCreateSelfSignedCertificate()
|
||||||
|
|
||||||
|
provisionNginxStandAlone(
|
||||||
|
NginxConf.nginxReverseProxySslConfig(
|
||||||
|
"localhost",
|
||||||
|
dirSslCert + "/" + certificateName + ".crt",
|
||||||
|
dirSslKey + "/" + certificateName + ".key"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createFile
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.fileContainsText
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.configFile
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.server_software.nginx.provisionNginxStandAlone
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.NonCi
|
||||||
|
|
||||||
|
internal class LocationsKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@NonCi
|
||||||
|
fun nginxIncludeLocationFolders() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
a.provisionNginxStandAlone()
|
||||||
|
a.createFile(configFile, NGINX_MINIMAL_CONF, sudo = true)
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.nginxIncludeLocationFolders()
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
assertTrue(a.fileContainsText(
|
||||||
|
configFile, """listen 80;
|
||||||
|
include /etc/nginx/locations-enabled/port80*.conf
|
||||||
|
include /etc/nginx/locations-enabled/port443*.conf"""))
|
||||||
|
// just 1 occurrence
|
||||||
|
assertEquals("1", a.cmd("grep -o 'listen 80;' $configFile | wc -l").out?.trim())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.test_keys
|
||||||
|
|
||||||
|
|
||||||
|
fun publicGPGSnakeoilKey(): String {
|
||||||
|
return """-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
KpkCQg==
|
||||||
|
=cS1b
|
||||||
|
-----END PGP PUBLIC KEY BLOCK----- """
|
||||||
|
}
|
||||||
|
|
||||||
|
fun privateGPGSnakeoilKey(): String {
|
||||||
|
return """-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
i+XV3Dazj3nq/DxUB0neLU/r1afAEiqZAkI=
|
||||||
|
=h5SJ
|
||||||
|
-----END PGP PRIVATE KEY BLOCK-----""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun publicSSHSnakeoilKey(): String {
|
||||||
|
return """ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDOtQOq8a/Z7SdZVPrh+Icaq5rr+Qg1TZP4IPuRoFgfujUztQ2dy5DfTEbabJ0qHyo+PKwBDQorVohrW7CwvCEVQQh2NLuGgnukBN2ut5Lam7a/fZBoMjAyTvD4bXyEsUr/Bl5CLoBDkKM0elUxsc19ndzSofnDWeGyQjJIWlkNkVk/ybErAnIHVE+D+g3UxwA+emd7BF72RPqdVN39Eu4ntnxYzX0eepc8rkpFolVn6+Ai4CYHE4FaJ7bJ9WGPbwLuDl0pw/Cp3ps17cB+JlQfJ2spOq0tTVk+GcdGnt+mq0WaOnvVeQsGJ2O1HpY3VqQd1AsC2UOyHhAQ00pw7Pi9 snake@oil.com"""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun privateSSHSnakeoilKey(): String {
|
||||||
|
return """
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAzrUDqvGv2e0nWVT64fiHGqua6/kINU2T+CD7kaBYH7o1M7UN
|
||||||
|
ncuQ30xG2mydKh8qPjysAQ0KK1aIa1uwsLwhFUEIdjS7hoJ7pATdrreS2pu2v32Q
|
||||||
|
aDIwMk7w+G18hLFK/wZeQi6AQ5CjNHpVMbHNfZ3c0qH5w1nhskIySFpZDZFZP8mx
|
||||||
|
KwJyB1RPg/oN1McAPnpnewRe9kT6nVTd/RLuJ7Z8WM19HnqXPK5KRaJVZ+vgIuAm
|
||||||
|
BxOBWie2yfVhj28C7g5dKcPwqd6bNe3AfiZUHydrKTqtLU1ZPhnHRp7fpqtFmjp7
|
||||||
|
1XkLBidjtR6WN1akHdQLAtlDsh4QENNKcOz4vQIDAQABAoIBAGrgAsZ28gJOcSLq
|
||||||
|
IlGF62zpv0800n6k3tXTT98qtYWqBGn4udKVdxFNYfD7aYNm27OUMSbV9CUWN7Cy
|
||||||
|
lre6fax8lIBxoWfZvU2/ylLUzZREIIf/xxNop6zLTiJUkaYV+P3E8CVt35mPhiLT
|
||||||
|
AYuRL/s8DPnHD9lmdqBxQ4hPVm4Bg7JZxbyN8in3PP1UkdWKxg91O1LYewIZHszq
|
||||||
|
y9BdklKyxQ+fcYP5DD9KkULAjdab48GIxQETrZKp7zV0KiGrjF4Axf5y5yT2jmFT
|
||||||
|
nZ1uZrC1MJTMYyKTBR7wsSpVBMSMUsh5XtxdJo4FuP6g9Kn6AkeQ/Y1shcWVfQgw
|
||||||
|
6009o8ECgYEA8J1PtnVCHxMLiVKZznzvgCe+EV0RkvuB9PGPdfpLfkHa1DKS+FzH
|
||||||
|
80D+Vqe0rQNLudG5Qj53MPghNirGyrjXwTYFW9xCqq9hrzfxEI4xIYOd4gHoPMMQ
|
||||||
|
pfWZylP9GYQp/uoa+e/fcdXRSv1IDLRwJZ5XpMtWAIfvMOyDhbfjehECgYEA2+yp
|
||||||
|
poey1y6RWuaIQd2a/PKuYk9jvLEETiz6q7t63MFd6e9cUYX02cG/6yzz6piTWUtx
|
||||||
|
pk9e9IjclLUgV/twVz8SUgSw5TcqBrMnuIT4yQ5rQNZqiEvpCfgb5itcW7I3ADGy
|
||||||
|
dsz2kgaAm7QVZlndQKIy7xRYBCnCD3VQ+TiWh+0CgYAT3qnKg3xmXIhDWtLgvmh4
|
||||||
|
yM9lV64v2R0uQRR7xaOeVYngpByG7gKFEATw2wCMmQ0T10HZOpdVL+huNLId443N
|
||||||
|
osxmfZXzym/irFf36gYcomXTWBz5h5JEYjfFAZKRHNzq9CIuKaTmHaYe7zOX+P6Z
|
||||||
|
3K2YKkJ74L3b6GwkCr96QQKBgQC6n0iTTSGg4h5skaXcpq2HqnP6br4G9/vcTuTk
|
||||||
|
Z/JpdBk6k2i2sULGqlguu/W8BH89Tf0CEOZWAfGUq2Ln5jE9iAMG4H4v9DDQgKTb
|
||||||
|
OtNW4cp3uburLydw0z7xgagdE80CeCmmEGXIIoZuGlHyiZ1r5HfuU0ghOEI6FeaB
|
||||||
|
pdhvPQKBgEpmHV66wqSzzxmYxKjUu8gl9rIniG8SWXHlvcoGVwt1qdOMtNtvwDgB
|
||||||
|
DnbUbANSjzIfFSqVwlx7nXG1e1yN7F1YuyUa3I5QEm4+5URoTSDghk03LTFH+kfM
|
||||||
|
OUxwE8Su4WnoQc7WjkTG0M3FECAu7TEcF9uqdcEsW+4+JMAhE5oo
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Password
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
|
||||||
|
internal class ProvisionWorkplaceKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun provisionWorkplace() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
// in order to test WorkplaceType.OFFICE: fix installing libreoffice for a fresh container as it hangs the first time but succeeds 2nd time
|
||||||
|
val res = a.provisionWorkplace(
|
||||||
|
WorkplaceType.MINIMAL,
|
||||||
|
gitUserName = "testuser",
|
||||||
|
gitEmail = "testuser@test.org",
|
||||||
|
userPassword = Password("testuser")
|
||||||
|
)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun provisionWorkplaceFromConfigFile() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
// in order to test WorkplaceType.OFFICE: fix installing libreoffice for a fresh container as it hangs the first time but succeeds 2nd time
|
||||||
|
val config = readWorkplaceConfigFromFile("src/test/resources/WorkplaceConfigExample.json")
|
||||||
|
?: throw Exception("Could not read WorkplaceConfig")
|
||||||
|
val res = a.provisionWorkplace(
|
||||||
|
config.type,
|
||||||
|
config.ssh?.keyPair(),
|
||||||
|
config.gpg?.keyPair(),
|
||||||
|
config.gitUserName,
|
||||||
|
config.gitEmail,
|
||||||
|
)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class FakturamaKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun installFakturama() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
// when
|
||||||
|
val res = a.def { installFakturama() }
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.exitAndRmContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.core.local
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.ContainerStartMode
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.NonCi
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.KeyPair
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.base.configureGpgKeys
|
||||||
|
import org.junit.jupiter.api.Assertions.assertFalse
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.test_keys.privateGPGSnakeoilKey
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.test_keys.publicGPGSnakeoilKey
|
||||||
|
|
||||||
|
|
||||||
|
internal class GopassBridgeKtTest {
|
||||||
|
|
||||||
|
@ContainerTest
|
||||||
|
@Test
|
||||||
|
fun test_downloadGopassBridge() {
|
||||||
|
// given
|
||||||
|
local().exitAndRmContainer("provs_test")
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
a.aptInstallCurl()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.downloadGopassBridge()
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContainerTest
|
||||||
|
@Test
|
||||||
|
fun test_install_and_configure_GopassBridgeJsonApi() {
|
||||||
|
// given
|
||||||
|
local().exitAndRmContainer("provs_test")
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
val preparationResult = a.def {
|
||||||
|
aptInstallCurl()
|
||||||
|
configureGpgKeys(
|
||||||
|
KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())),
|
||||||
|
trust = true,
|
||||||
|
skipIfExistin = false
|
||||||
|
)
|
||||||
|
installGopass()
|
||||||
|
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)
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.def {
|
||||||
|
installGopassBridgeJsonApi()
|
||||||
|
configureGopassBridgeJsonApi()
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContainerTest
|
||||||
|
@Test
|
||||||
|
@NonCi
|
||||||
|
fun test_install_GopassBridgeJsonApi_with_incompatible_gopass_jsonapi_version_installed() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer(ContainerStartMode.CREATE_NEW_KILL_EXISTING)
|
||||||
|
val preparationResult = a.def {
|
||||||
|
aptInstallCurl()
|
||||||
|
|
||||||
|
configureGpgKeys(
|
||||||
|
KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())),
|
||||||
|
trust = true,
|
||||||
|
skipIfExistin = false
|
||||||
|
)
|
||||||
|
installGopass("1.11.0", enforceVersion = true)
|
||||||
|
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)
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.def {
|
||||||
|
installGopassBridgeJsonApi()
|
||||||
|
configureGopassBridgeJsonApi()
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertFalse(res.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContainerTest
|
||||||
|
@Test
|
||||||
|
@NonCi
|
||||||
|
fun test_install_GopassBridgeJsonApi_with_incompatible_gopass_version_installed() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer(ContainerStartMode.CREATE_NEW_KILL_EXISTING)
|
||||||
|
val preparationResult = a.def {
|
||||||
|
aptInstallCurl()
|
||||||
|
configureGpgKeys(
|
||||||
|
KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())),
|
||||||
|
trust = true,
|
||||||
|
skipIfExistin = false
|
||||||
|
)
|
||||||
|
installGopass("1.9.0", enforceVersion = true)
|
||||||
|
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)
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.def {
|
||||||
|
installGopassBridgeJsonApi()
|
||||||
|
configureGopassBridgeJsonApi()
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertFalse(res.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Prov.aptInstallCurl() = def {
|
||||||
|
cmd("apt-get update", sudo = true)
|
||||||
|
aptInstall("curl")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.core.remote
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.*
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.KeyPair
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.base.configureGpgKeys
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.base.gpgFingerprint
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.secret.secretSources.GopassSecretSource
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Disabled
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.test_keys.privateGPGSnakeoilKey
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.extensions.test_keys.publicGPGSnakeoilKey
|
||||||
|
|
||||||
|
|
||||||
|
internal class GopassKtTest {
|
||||||
|
|
||||||
|
@ContainerTest
|
||||||
|
@Test
|
||||||
|
fun test_installAndConfigureGopassAndMountStore() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
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
|
||||||
|
val res = a.installGopass()
|
||||||
|
val res2 = a.configureGopass(a.userHome() + gopassRootDir)
|
||||||
|
val res3 = a.gopassMountStore("exampleStore", "~/exampleStoreFolder")
|
||||||
|
|
||||||
|
// then
|
||||||
|
a.fileContent("~/.config/gopass/config.yml") // displays the content in the logs
|
||||||
|
assertTrue(res.success)
|
||||||
|
assertTrue(res2.success)
|
||||||
|
assertTrue(res3.success)
|
||||||
|
assertTrue(a.fileContainsText("~/.config/gopass/config.yml", "/home/testuser/.password-store"))
|
||||||
|
assertTrue(a.fileContainsText("~/.config/gopass/config.yml", "exampleStore"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled // Integrationtest; change user, host and keys, then remove this line to run this test
|
||||||
|
fun test_install_and_configure_Gopass_and_GopassBridgeJsonApi() {
|
||||||
|
// settings to change
|
||||||
|
val host = "192.168.56.135"
|
||||||
|
val user = "xxx"
|
||||||
|
val pubKey = GopassSecretSource("path-to/pub.key").secret()
|
||||||
|
val privateKey = GopassSecretSource("path-to/priv.key").secret()
|
||||||
|
|
||||||
|
// given
|
||||||
|
val a = remote(host, user)
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.def {
|
||||||
|
configureGpgKeys(
|
||||||
|
KeyPair(
|
||||||
|
pubKey,
|
||||||
|
privateKey
|
||||||
|
),
|
||||||
|
trust = true,
|
||||||
|
skipIfExistin = true
|
||||||
|
)
|
||||||
|
installGopass()
|
||||||
|
|
||||||
|
if (!chk("gopass ls")) {
|
||||||
|
// configure (=init) gopass
|
||||||
|
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()
|
||||||
|
installGopassBridgeJsonApi()
|
||||||
|
configureGopassBridgeJsonApi()
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.extensions.workplace.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Disabled
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
|
||||||
|
|
||||||
|
internal class VSCodeKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled("Test currently not working, needs fix. VSC is installed by snapd which is not currently supported to run inside docker")
|
||||||
|
fun installVSC() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
a.aptInstall("xvfb libgbm-dev libasound2")
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.installVSC("python", "clojure")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,194 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.filesystem.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
|
||||||
|
internal class FilesystemKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ContainerTest
|
||||||
|
fun checkingCreatingDeletingFile() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res1 = prov.fileExists("testfile")
|
||||||
|
val res2 = prov.createFile("testfile", "some content")
|
||||||
|
val res3 = prov.fileExists("testfile")
|
||||||
|
val res4a = prov.fileContainsText("testfile", "some content")
|
||||||
|
val res4b = prov.fileContainsText("testfile", "some non-existing content")
|
||||||
|
val res5 = prov.deleteFile("testfile")
|
||||||
|
val res6 = prov.fileExists("testfile")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertFalse(res1)
|
||||||
|
assertTrue(res2.success)
|
||||||
|
assertTrue(res3)
|
||||||
|
assertTrue(res4a)
|
||||||
|
assertFalse(res4b)
|
||||||
|
assertTrue(res5.success)
|
||||||
|
assertFalse(res6)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ContainerTest
|
||||||
|
fun checkingCreatingDeletingFileWithSudo() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val file = "/testfile"
|
||||||
|
val res1 = prov.fileExists(file)
|
||||||
|
val res2 = prov.createFile(file, "some content", sudo = true)
|
||||||
|
val res3 = prov.fileExists(file)
|
||||||
|
val res4a = prov.fileContainsText(file, "some content")
|
||||||
|
val res4b = prov.fileContainsText(file, "some non-existing content")
|
||||||
|
val res5 = prov.deleteFile(file)
|
||||||
|
val res6 = prov.fileExists(file)
|
||||||
|
val res7 = prov.deleteFile(file, true)
|
||||||
|
val res8 = prov.fileExists(file)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertFalse(res1)
|
||||||
|
assertTrue(res2.success)
|
||||||
|
assertTrue(res3)
|
||||||
|
assertTrue(res4a)
|
||||||
|
assertFalse(res4b)
|
||||||
|
assertFalse(res5.success)
|
||||||
|
assertTrue(res6)
|
||||||
|
assertTrue(res7.success)
|
||||||
|
assertFalse(res8)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ContainerTest
|
||||||
|
fun checkingCreatingDeletingDir() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res1 = prov.dirExists("testdir")
|
||||||
|
val res2 = prov.createDir("testdir", "~/")
|
||||||
|
val res3 = prov.dirExists("testdir")
|
||||||
|
val res4 = prov.deleteDir("testdir", "~/")
|
||||||
|
val res5 = prov.dirExists("testdir")
|
||||||
|
|
||||||
|
val res6 = prov.dirExists("testdir", "~/test")
|
||||||
|
val res7 = prov.createDirs("test/testdir")
|
||||||
|
val res8 = prov.dirExists("testdir", "~/test")
|
||||||
|
prov.deleteDir("testdir", "~/test/")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertFalse(res1)
|
||||||
|
assertTrue(res2.success)
|
||||||
|
assertTrue(res3)
|
||||||
|
assertTrue(res4.success)
|
||||||
|
assertFalse(res5)
|
||||||
|
assertFalse(res6)
|
||||||
|
assertTrue(res7.success)
|
||||||
|
assertTrue(res8)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ContainerTest
|
||||||
|
fun checkingCreatingDeletingDirWithSudo() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res1 = prov.dirExists("/testdir", sudo = true)
|
||||||
|
val res2 = prov.createDir("testdir", "/", sudo = true)
|
||||||
|
val res3 = prov.dirExists("/testdir", sudo = true)
|
||||||
|
val res4 = prov.deleteDir("testdir", "/", true)
|
||||||
|
val res5 = prov.dirExists("testdir", sudo = true)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertFalse(res1)
|
||||||
|
assertTrue(res2.success)
|
||||||
|
assertTrue(res3)
|
||||||
|
assertTrue(res4.success)
|
||||||
|
assertFalse(res5)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun userHome() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res1 = prov.userHome()
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals("/home/testuser/", res1)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ContainerTest
|
||||||
|
fun replaceTextInFile() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val file = "replaceTest"
|
||||||
|
val res1 = prov.createFile(file, "a\nb\nc\nd")
|
||||||
|
val res2 = prov.replaceTextInFile(file,"b", "hi\nho")
|
||||||
|
val res3 = prov.fileContent(file).equals("a\nhi\nho\nc\nd")
|
||||||
|
val res4 = prov.deleteFile(file)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res1.success)
|
||||||
|
assertTrue(res2.success)
|
||||||
|
assertTrue(res3)
|
||||||
|
assertTrue(res4.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ContainerTest
|
||||||
|
fun replaceTextInFileRegex() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val file = "replaceTest"
|
||||||
|
val res1 = prov.createFile(file, "a\nbananas\nc\nd")
|
||||||
|
val res2 = prov.replaceTextInFile(file, Regex("b.*n?nas\n"), "hi\nho\n")
|
||||||
|
val res3 = prov.fileContent(file)
|
||||||
|
val res4 = prov.deleteFile(file)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res1.success)
|
||||||
|
assertTrue(res2.success)
|
||||||
|
assertEquals("a\nhi\nho\nc\nd",res3)
|
||||||
|
assertTrue(res4.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ContainerTest
|
||||||
|
fun insertTextInFile() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val file = "insertTest"
|
||||||
|
val res1 = prov.createFile(file, "a\nbananas\nc\nd")
|
||||||
|
val res2 = prov.insertTextInFile(file, Regex("b.*n.nas\n"), "hi\n")
|
||||||
|
val res3 = prov.fileContent(file)
|
||||||
|
val res4 = prov.deleteFile(file)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res1.success)
|
||||||
|
assertTrue(res2.success)
|
||||||
|
assertEquals("a\nbananas\nhi\nc\nd", res3)
|
||||||
|
assertTrue(res4.success)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.git.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.base.isHostKnown
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
|
||||||
|
internal class GitKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun trustGitServers(){
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
a.aptInstall("openssh-client")
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.trustGithub()
|
||||||
|
val known = a.isHostKnown("github.com")
|
||||||
|
val res2 = a.trustGitlab()
|
||||||
|
val known2 = a.isHostKnown("gitlab.com")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
assertTrue(known)
|
||||||
|
assertTrue(res2.success)
|
||||||
|
assertTrue(known2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun gitClone() {
|
||||||
|
// given
|
||||||
|
val prov = defaultTestContainer()
|
||||||
|
prov.aptInstall("openssh-client ssh git")
|
||||||
|
|
||||||
|
// when
|
||||||
|
prov.trustGithub()
|
||||||
|
prov.gitClone("https://github.com/DomainDrivenArchitecture/dda-git-crate.git", "~/")
|
||||||
|
val res = prov.gitClone("https://github.com/DomainDrivenArchitecture/dda-git-crate.git", "~/")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.install.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Disabled
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
|
||||||
|
internal class InstallKtTest {
|
||||||
|
|
||||||
|
@ContainerTest
|
||||||
|
@Test
|
||||||
|
fun aptInstall_installsPackage() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.aptInstall("rolldice")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContainerTest
|
||||||
|
@Test
|
||||||
|
@Disabled // run manually if needed;
|
||||||
|
// todo: replace zim by a smaller repo
|
||||||
|
fun aptInstallFromPpa_installsPackage() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
a.aptInstall("software-properties-common") // prereq for adding a repo to apt
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.aptInstallFromPpa("jaap.karssenberg", "zim", "zim")
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.keys
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||||
|
import org.junit.jupiter.api.condition.OS
|
||||||
|
|
||||||
|
internal class ProvisionKeysTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
fun provisionKeysCurrentUser() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.provisionKeysCurrentUser(
|
||||||
|
KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())),
|
||||||
|
KeyPair(Secret(publicSSHSnakeoilKey()), Secret(privateSSHSnakeoilKey()))
|
||||||
|
)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res.success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.keys
|
||||||
|
|
||||||
|
|
||||||
|
fun publicGPGSnakeoilKey(): String {
|
||||||
|
return """-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
KpkCQg==
|
||||||
|
=cS1b
|
||||||
|
-----END PGP PUBLIC KEY BLOCK----- """
|
||||||
|
}
|
||||||
|
|
||||||
|
fun privateGPGSnakeoilKey(): String {
|
||||||
|
return """-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
i+XV3Dazj3nq/DxUB0neLU/r1afAEiqZAkI=
|
||||||
|
=h5SJ
|
||||||
|
-----END PGP PRIVATE KEY BLOCK-----""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun publicSSHSnakeoilKey(): String {
|
||||||
|
return """ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDOtQOq8a/Z7SdZVPrh+Icaq5rr+Qg1TZP4IPuRoFgfujUztQ2dy5DfTEbabJ0qHyo+PKwBDQorVohrW7CwvCEVQQh2NLuGgnukBN2ut5Lam7a/fZBoMjAyTvD4bXyEsUr/Bl5CLoBDkKM0elUxsc19ndzSofnDWeGyQjJIWlkNkVk/ybErAnIHVE+D+g3UxwA+emd7BF72RPqdVN39Eu4ntnxYzX0eepc8rkpFolVn6+Ai4CYHE4FaJ7bJ9WGPbwLuDl0pw/Cp3ps17cB+JlQfJ2spOq0tTVk+GcdGnt+mq0WaOnvVeQsGJ2O1HpY3VqQd1AsC2UOyHhAQ00pw7Pi9 snake@oil.com"""
|
||||||
|
}
|
||||||
|
|
||||||
|
fun privateSSHSnakeoilKey(): String {
|
||||||
|
return """
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAzrUDqvGv2e0nWVT64fiHGqua6/kINU2T+CD7kaBYH7o1M7UN
|
||||||
|
ncuQ30xG2mydKh8qPjysAQ0KK1aIa1uwsLwhFUEIdjS7hoJ7pATdrreS2pu2v32Q
|
||||||
|
aDIwMk7w+G18hLFK/wZeQi6AQ5CjNHpVMbHNfZ3c0qH5w1nhskIySFpZDZFZP8mx
|
||||||
|
KwJyB1RPg/oN1McAPnpnewRe9kT6nVTd/RLuJ7Z8WM19HnqXPK5KRaJVZ+vgIuAm
|
||||||
|
BxOBWie2yfVhj28C7g5dKcPwqd6bNe3AfiZUHydrKTqtLU1ZPhnHRp7fpqtFmjp7
|
||||||
|
1XkLBidjtR6WN1akHdQLAtlDsh4QENNKcOz4vQIDAQABAoIBAGrgAsZ28gJOcSLq
|
||||||
|
IlGF62zpv0800n6k3tXTT98qtYWqBGn4udKVdxFNYfD7aYNm27OUMSbV9CUWN7Cy
|
||||||
|
lre6fax8lIBxoWfZvU2/ylLUzZREIIf/xxNop6zLTiJUkaYV+P3E8CVt35mPhiLT
|
||||||
|
AYuRL/s8DPnHD9lmdqBxQ4hPVm4Bg7JZxbyN8in3PP1UkdWKxg91O1LYewIZHszq
|
||||||
|
y9BdklKyxQ+fcYP5DD9KkULAjdab48GIxQETrZKp7zV0KiGrjF4Axf5y5yT2jmFT
|
||||||
|
nZ1uZrC1MJTMYyKTBR7wsSpVBMSMUsh5XtxdJo4FuP6g9Kn6AkeQ/Y1shcWVfQgw
|
||||||
|
6009o8ECgYEA8J1PtnVCHxMLiVKZznzvgCe+EV0RkvuB9PGPdfpLfkHa1DKS+FzH
|
||||||
|
80D+Vqe0rQNLudG5Qj53MPghNirGyrjXwTYFW9xCqq9hrzfxEI4xIYOd4gHoPMMQ
|
||||||
|
pfWZylP9GYQp/uoa+e/fcdXRSv1IDLRwJZ5XpMtWAIfvMOyDhbfjehECgYEA2+yp
|
||||||
|
poey1y6RWuaIQd2a/PKuYk9jvLEETiz6q7t63MFd6e9cUYX02cG/6yzz6piTWUtx
|
||||||
|
pk9e9IjclLUgV/twVz8SUgSw5TcqBrMnuIT4yQ5rQNZqiEvpCfgb5itcW7I3ADGy
|
||||||
|
dsz2kgaAm7QVZlndQKIy7xRYBCnCD3VQ+TiWh+0CgYAT3qnKg3xmXIhDWtLgvmh4
|
||||||
|
yM9lV64v2R0uQRR7xaOeVYngpByG7gKFEATw2wCMmQ0T10HZOpdVL+huNLId443N
|
||||||
|
osxmfZXzym/irFf36gYcomXTWBz5h5JEYjfFAZKRHNzq9CIuKaTmHaYe7zOX+P6Z
|
||||||
|
3K2YKkJ74L3b6GwkCr96QQKBgQC6n0iTTSGg4h5skaXcpq2HqnP6br4G9/vcTuTk
|
||||||
|
Z/JpdBk6k2i2sULGqlguu/W8BH89Tf0CEOZWAfGUq2Ln5jE9iAMG4H4v9DDQgKTb
|
||||||
|
OtNW4cp3uburLydw0z7xgagdE80CeCmmEGXIIoZuGlHyiZ1r5HfuU0ghOEI6FeaB
|
||||||
|
pdhvPQKBgEpmHV66wqSzzxmYxKjUu8gl9rIniG8SWXHlvcoGVwt1qdOMtNtvwDgB
|
||||||
|
DnbUbANSjzIfFSqVwlx7nXG1e1yN7F1YuyUa3I5QEm4+5URoTSDghk03LTFH+kfM
|
||||||
|
OUxwE8Su4WnoQc7WjkTG0M3FECAu7TEcF9uqdcEsW+4+JMAhE5oo
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.keys.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.ContainerStartMode
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.KeyPair
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.privateGPGSnakeoilKey
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.publicGPGSnakeoilKey
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class GpgKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ContainerTest
|
||||||
|
fun gpgFingerprint_returnsCorrectFingerprint() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
a.aptInstall("gpg")
|
||||||
|
a.cmd("gpg --version") // just for info reasons
|
||||||
|
|
||||||
|
// when
|
||||||
|
val fingerprint = a.gpgFingerprint(publicGPGSnakeoilKey())
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals("85052C6954262D61D4E9977E0674104CA81A4905", fingerprint)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ContainerTest
|
||||||
|
fun configureGpgKeys() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.configureGpgKeys(KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())))
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ContainerTest
|
||||||
|
fun configureGpgKeysTrusted() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.configureGpgKeys(KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())), true)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
val trustedKey = a.cmd("gpg -K | grep ultimate").out
|
||||||
|
assertEquals("uid [ultimate] snakeoil <snake@oil.com>", trustedKey?.trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@ContainerTest
|
||||||
|
fun configureGpgKeysIsIdempotent() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.configureGpgKeys(KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())))
|
||||||
|
val res2 = a.configureGpgKeys(KeyPair(Secret(publicGPGSnakeoilKey()), Secret(privateGPGSnakeoilKey())))
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
assertTrue(res2.success)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.keys.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Secret
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.*
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
|
||||||
|
internal class SshKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun configureSshKeys() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.configureSshKeys(KeyPair(Secret(publicSSHSnakeoilKey()), Secret(privateSSHSnakeoilKey())))
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertTrue(res.success)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.secret.secretSources
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Disabled
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class PromptSecretSourceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Disabled // run manually
|
||||||
|
fun secret() {
|
||||||
|
println("Secret: " + PromptSecretSource().secret().plain())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.user
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.keys.*
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.secret.SecretSourceType
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.user.base.configureUser
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||||
|
import org.junit.jupiter.api.condition.OS
|
||||||
|
|
||||||
|
|
||||||
|
internal class ProvisionUserKtTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@EnabledOnOs(OS.LINUX)
|
||||||
|
fun configureUser() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.configureUser(
|
||||||
|
UserConfig(
|
||||||
|
"testuser",
|
||||||
|
"test@mail.com",
|
||||||
|
KeyPairSource(SecretSourceType.PLAIN, publicGPGSnakeoilKey(), privateGPGSnakeoilKey()),
|
||||||
|
KeyPairSource(SecretSourceType.PLAIN, publicSSHSnakeoilKey(), privateSSHSnakeoilKey())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert(res.success)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.utils
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class UtilsKtTest {
|
||||||
|
|
||||||
|
@ContainerTest
|
||||||
|
@Test
|
||||||
|
fun printToShell_escapes_successfully() {
|
||||||
|
// given
|
||||||
|
val a = Prov.defaultInstance()
|
||||||
|
|
||||||
|
// when
|
||||||
|
val testString = "test if newline \n and apostrophe's ' \" and special chars !§$%[]\\ äöüß are handled correctly"
|
||||||
|
val res = a.cmd(printToShell(testString)).out
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(testString, res)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.ubuntu.web.base
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createFile
|
||||||
|
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.fileContent
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
internal class WebKtTest {
|
||||||
|
|
||||||
|
@ContainerTest
|
||||||
|
@Test
|
||||||
|
fun downloadFromURL_downloadsFile() {
|
||||||
|
// given
|
||||||
|
val a = defaultTestContainer()
|
||||||
|
val file = "file1"
|
||||||
|
a.createFile("/tmp/" + file, "hello")
|
||||||
|
|
||||||
|
// when
|
||||||
|
val res = a.downloadFromURL("file:///tmp/" + file, "file2", "/tmp")
|
||||||
|
|
||||||
|
// then
|
||||||
|
val res2 = a.fileContent("/tmp/file2")
|
||||||
|
|
||||||
|
assertTrue(res.success)
|
||||||
|
assertEquals("hello", res2)
|
||||||
|
}
|
||||||
|
}
|
19
bin/testFixtures/logback-test.xml
Normal file
19
bin/testFixtures/logback-test.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<timestamp key="byTime" datePattern="yyyyMMdd'T'HHmmss"/>
|
||||||
|
|
||||||
|
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<target>System.err</target>
|
||||||
|
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{35}) - %msg %n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="net.schmizz" level="ERROR"/>
|
||||||
|
|
||||||
|
<root level="ALL">
|
||||||
|
<appender-ref ref="STDERR" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.domaindrivenarchitecture.provs.test
|
||||||
|
|
||||||
|
import org.domaindrivenarchitecture.provs.core.ProgressType
|
||||||
|
import org.domaindrivenarchitecture.provs.core.Prov
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.dockerImageExists
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.dockerProvideImage
|
||||||
|
import org.domaindrivenarchitecture.provs.core.docker.dockerimages.UbuntuPlusUser
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.ContainerStartMode
|
||||||
|
import org.domaindrivenarchitecture.provs.core.processors.ContainerUbuntuHostProcessor
|
||||||
|
|
||||||
|
val testDockerWithSudo = !"true".equals(System.getProperty("testdockerwithoutsudo")?.toLowerCase())
|
||||||
|
|
||||||
|
const val defaultTestContainerName = "provs_test"
|
||||||
|
|
||||||
|
fun defaultTestContainer(startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE): Prov {
|
||||||
|
val image = UbuntuPlusUser()
|
||||||
|
val prov = testLocal()
|
||||||
|
if (!prov.dockerImageExists(image.imageName(), testDockerWithSudo)) {
|
||||||
|
prov.dockerProvideImage(image, sudo = testDockerWithSudo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Prov.newInstance(
|
||||||
|
ContainerUbuntuHostProcessor(
|
||||||
|
defaultTestContainerName,
|
||||||
|
startMode = startMode,
|
||||||
|
sudo = testDockerWithSudo,
|
||||||
|
dockerImage = image.imageName()
|
||||||
|
),
|
||||||
|
progressType = ProgressType.NONE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testLocal(): Prov {
|
||||||
|
return Prov.newInstance(name = "testing", progressType = ProgressType.NONE)
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue