add task without requiring an explicit return value (ProvResult)

This commit is contained in:
ansgarz 2022-04-18 18:45:52 +02:00
parent 7eb7494fad
commit efe158275b
15 changed files with 52 additions and 44 deletions

View file

@ -12,13 +12,13 @@ fun Prov.installGopass(
version: String = "1.12.7",
enforceVersion: Boolean = false,
sha256sum: String = "0824d5110ff1e68bff1ba10c1be63acb67cb1ad8e3bccddd6b6fc989608beca8" // checksum for sha256sum version 8.30 (e.g. ubuntu 20.04)
) = task {
) = taskWithResult {
if (isPackageInstalled("gopass") && !enforceVersion) {
return@task ProvResult(true)
return@taskWithResult ProvResult(true)
}
if (checkGopassVersion(version)) {
return@task ProvResult(true, out = "Version $version of gopass is already installed.")
return@taskWithResult ProvResult(true, out = "Version $version of gopass is already installed.")
}
val path = "tmp"
@ -41,17 +41,17 @@ fun Prov.installGopass(
}
fun Prov.configureGopass(gopassRootFolder: String? = null) = task {
fun Prov.configureGopass(gopassRootFolder: String? = null) = taskWithResult() {
val configFile = ".config/gopass/config.yml"
val defaultRootFolder = userHome() + ".password-store"
val rootFolder = gopassRootFolder ?: defaultRootFolder
if (checkFile(configFile)) {
return@task ProvResult(true, out = "Gopass already configured in file $configFile")
return@taskWithResult ProvResult(true, out = "Gopass already configured in file $configFile")
}
if ((gopassRootFolder != null) && (!gopassRootFolder.startsWith("/"))) {
return@task ProvResult(false, err = "Gopass cannot be initialized with a relative path or path starting with ~")
return@taskWithResult ProvResult(false, err = "Gopass cannot be initialized with a relative path or path starting with ~")
}
// use default
createDir(rootFolder)

View file

@ -5,14 +5,14 @@ import org.domaindrivenarchitecture.provs.framework.core.ProvResult
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.whoami
fun Prov.installVirtualBoxGuestAdditions() = task {
fun Prov.installVirtualBoxGuestAdditions() = taskWithResult {
// if running in a VirtualBox vm
if (!chk("lspci | grep VirtualBox")) {
return@task ProvResult(true, "Not running in a VirtualBox")
return@taskWithResult ProvResult(true, "Not running in a VirtualBox")
}
if (chk("VBoxService --version")) {
return@task ProvResult(true, "VBoxService already installed")
return@taskWithResult ProvResult(true, "VBoxService already installed")
}
// install guest additions

View file

@ -67,11 +67,19 @@ open class Prov protected constructor(
private var runInContainerWithName: String? = null
/**
* Defines a task with a custom name instead of the name of the calling function.
* Returns success if all subtasks finished with success (same as requireAll).
* A task is the base execution unit in provs. In the results overview it is represented by one line resp. result (of either success or failure).
* Returns success if no sub-tasks are called or if all subtasks finish with success.
*/
fun task(name: String? = null, a: Prov.() -> ProvResult): ProvResult {
return handle(ResultMode.ALL, name) { a() }
fun task(name: String? = null, taskLambda: Prov.() -> Unit): ProvResult {
return handle(ResultMode.ALL, name) { taskLambda(); ProvResult(true) }
}
/**
* Same as task but the provided lambda is explicitly required to provide a ProvResult to be returned.
* The returned result is included in the evaluation.
*/
fun taskWithResult(name: String? = null, taskLambda: Prov.() -> ProvResult): ProvResult {
return handle(ResultMode.ALL, name) { taskLambda() }
}
/**
@ -84,7 +92,7 @@ open class Prov protected constructor(
}
/**
* defines a task, which returns success if the the last subtasks or last value returns success
* defines a task, which returns the returned result, the results of sub-tasks are not considered
*/
fun requireLast(a: Prov.() -> ProvResult): ProvResult {
return handle(ResultMode.LAST) { a() }
@ -100,7 +108,7 @@ open class Prov protected constructor(
/**
* defines a task, which returns success if all subtasks finished with success
*/
@Suppress("unused")
@Deprecated("Use function task instead", replaceWith = ReplaceWith("task()"))
fun requireAll(a: Prov.() -> ProvResult): ProvResult {
return handle(ResultMode.ALL) { a() }
}
@ -218,7 +226,7 @@ open class Prov protected constructor(
* Adds a ProvResult to the overall success evaluation.
* Intended for use in methods which do not automatically add results.
*/
fun addResultToEval(result: ProvResult) = task {
fun addResultToEval(result: ProvResult) = taskWithResult {
result
}
@ -227,7 +235,7 @@ open class Prov protected constructor(
* 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) = task {
fun sh(script: String, dir: String? = null, sudo: Boolean = false) = taskWithResult {
val lines = script.trimIndent().replace("\\\n", "").replace("\r\n", "\n").split("\n")
val linesWithoutComments = lines.stream().map { it.split("#")[0] }
val linesNonEmpty = linesWithoutComments.filter { it.trim().isNotEmpty() }

View file

@ -18,7 +18,7 @@ import java.net.InetAddress
*/
internal fun getCallingMethodName(): String? {
val offsetVal = 1
val exclude = arrayOf("task", "def", "record", "invoke", "invoke0", "handle", "task\$default", "def\$default", "addResultToEval", "handle\$default")
val exclude = arrayOf("task", "task\$default", "taskWithResult\$default", "taskWithResult", "def", "def\$default", "record", "invoke", "invoke0", "handle", "handle\$default", )
// suffixes are also ignored as method names but will be added as suffix in the evaluation results
val suffixes = arrayOf("optional", "requireAll", "requireLast", "inContainer")

View file

@ -23,7 +23,7 @@ class UbuntuProv internal constructor(
}
}
override fun cmd(cmd: String, dir: String?, sudo: Boolean): ProvResult = task {
override fun cmd(cmd: String, dir: String?, sudo: Boolean): ProvResult = taskWithResult {
exec(SHELL, "-c", commandWithDirAndSudo(cmd, dir, sudo))
}

View file

@ -90,7 +90,7 @@ fun Prov.createFile(
posixFilePermission: String? = null,
sudo: Boolean = false,
overwriteIfExisting: Boolean = true
): ProvResult = task {
): ProvResult = taskWithResult {
val maxBlockSize = 50000
val withSudo = if (sudo) "sudo " else ""
@ -98,7 +98,7 @@ fun Prov.createFile(
ensureValidPosixFilePermission(posixFilePermission)
}
if (!overwriteIfExisting && checkFile(fullyQualifiedFilename, sudo)) {
return@task ProvResult(true, "File $fullyQualifiedFilename already existing.")
return@taskWithResult ProvResult(true, "File $fullyQualifiedFilename already existing.")
}
val modeOption = posixFilePermission?.let { "-m $it" } ?: ""
@ -220,10 +220,10 @@ fun Prov.addTextToFile(
doNotAddIfExisting: Boolean = true,
sudo: Boolean = false
): ProvResult =
task {
taskWithResult {
val fileContainsText = fileContainsText(file.path, text, sudo = sudo)
if (fileContainsText && doNotAddIfExisting) {
return@task ProvResult(true, out = "Text already in file")
return@taskWithResult ProvResult(true, out = "Text already in file")
}
cmd(
"printf '%s' " + text

View file

@ -17,11 +17,11 @@ fun Prov.gitClone(
targetPath: String = "",
pullIfExisting: Boolean = true,
targetFolderName: String? = null
): ProvResult = task {
): ProvResult = taskWithResult {
// if specified, use targetFolderName as basename or otherwise retrieve basename from repoSource
val basename = targetFolderName ?: cmdNoEval("basename $repoSource .git").out?.trim()
// return err if basename could not be retrieved from repoSource
?: return@task ProvResult(false, err = "$repoSource is not a valid git repository source path.")
?: return@taskWithResult ProvResult(false, err = "$repoSource is not a valid git repository source path.")
val pathWithBasename = targetPath.normalizePath() + basename
if (checkDir(pathWithBasename + "/.git/")) {

View file

@ -13,7 +13,7 @@ private var aptInit = false
* @param ignoreAlreadyInstalled if true, then for an already installed package no action will be taken,
* if "ignoreAlreadyInstalled" is false, then installation is always attempted, which normally results in an upgrade if package wa already installed
*/
fun Prov.aptInstall(packages: String, ignoreAlreadyInstalled: Boolean = true): ProvResult = task {
fun Prov.aptInstall(packages: String, ignoreAlreadyInstalled: Boolean = true): ProvResult = taskWithResult {
val packageList = packages.split(" ")
val allInstalled: Boolean = packageList.map { isPackageInstalled(it) }.fold(true, { a, b -> a && b })
if (!allInstalled) {

View file

@ -38,9 +38,9 @@ fun Prov.isHostKnown(hostOrIp: String) : Boolean {
* Either add the specified rsaFingerprints or - if null - add automatically retrieved keys.
* Note: adding keys automatically is vulnerable to a man-in-the-middle attack, thus considered insecure and not recommended.
*/
fun Prov.trustHost(host: String, fingerprintsOfKeysToBeAdded: Set<String>?) = task {
fun Prov.trustHost(host: String, fingerprintsOfKeysToBeAdded: Set<String>?) = taskWithResult {
if (isHostKnown(host)) {
return@task ProvResult(true, out = "Host already known")
return@taskWithResult ProvResult(true, out = "Host already known")
}
if (!checkFile(KNOWN_HOSTS_FILE)) {
createDir(".ssh")
@ -53,7 +53,7 @@ fun Prov.trustHost(host: String, fingerprintsOfKeysToBeAdded: Set<String>?) = ta
// logic based on https://serverfault.com/questions/447028/non-interactive-git-clone-ssh-fingerprint-prompt
val actualKeys = findSshKeys(host)
if (actualKeys == null || actualKeys.size == 0) {
return@task ProvResult(false, out = "No valid keys found for host: $host")
return@taskWithResult ProvResult(false, out = "No valid keys found for host: $host")
}
val actualFingerprints = getFingerprintsForKeys(actualKeys)
for (fingerprintToBeAdded in fingerprintsOfKeysToBeAdded) {
@ -67,7 +67,7 @@ fun Prov.trustHost(host: String, fingerprintsOfKeysToBeAdded: Set<String>?) = ta
}
}
if (indexOfKeyFound == -1) {
return@task ProvResult(
return@taskWithResult ProvResult(
false,
err = "Fingerprint ($fingerprintToBeAdded) could not be found in actual fingerprints: $actualFingerprints"
)

View file

@ -22,13 +22,13 @@ fun Prov.downloadFromURL(
followRedirect: Boolean = true,
sha256sum: String? = null,
overwrite: Boolean = false
): ProvResult = task {
): ProvResult = taskWithResult {
val finalFilename: String = filename ?: url.substringAfterLast("/")
val fqFilename: String = (path?.normalizePath() ?: "") + finalFilename
if (!overwrite && checkFile(fqFilename, sudo = sudo)) {
return@task ProvResult(true, out = "File $fqFilename already exists.")
return@taskWithResult ProvResult(true, out = "File $fqFilename already exists.")
}
aptInstall("curl")

View file

@ -51,9 +51,9 @@ fun Prov.deprovisionK3sInfra() = task {
}
fun Prov.installK3s(k3sConfig: K3sConfig) = task {
fun Prov.installK3s(k3sConfig: K3sConfig) = taskWithResult {
if (testConfigExists()) {
return@task ProvResult(true, out = "K3s config is already in place, so skip (re)provisioning.")
return@taskWithResult ProvResult(true, out = "K3s config is already in place, so skip (re)provisioning.")
}
createDirs(k8sCredentialsDir, sudo = true)

View file

@ -8,17 +8,17 @@ import org.domaindrivenarchitecture.provs.syspec.infrastructure.findSpecConfigFr
import org.domaindrivenarchitecture.provs.syspec.infrastructure.verifySpecConfig
fun Prov.verifySpec(configFile: ConfigFileName? = null) = task {
fun Prov.verifySpec(configFile: ConfigFileName? = null) = taskWithResult {
val result = findSpecConfigFromFile(configFile)
val spec = result.getOrElse { return@task ProvResult(false, "Could not read file: ${configFile?.fileName} due to: ${result.exceptionOrNull()?.message}") }
val spec = result.getOrElse { return@taskWithResult ProvResult(false, "Could not read file: ${configFile?.fileName} due to: ${result.exceptionOrNull()?.message}") }
verifySpecConfig(spec)
}
@Suppress("unused") // Api
fun Prov.verifySpecFromResource(resourceName: String) = task {
fun Prov.verifySpecFromResource(resourceName: String) = taskWithResult {
val result = findSpecConfigFromResource(resourceName)
val spec = result.getOrElse { return@task ProvResult(false, "Could not read resource: $resourceName due to: ${result.exceptionOrNull()?.message}") }
val spec = result.getOrElse { return@taskWithResult ProvResult(false, "Could not read resource: $resourceName due to: ${result.exceptionOrNull()?.message}") }
verifySpecConfig(spec)
}

View file

@ -149,7 +149,7 @@ private fun <T> Prov.verify(success: Boolean, message: String, expected: T? = nu
val actualText = expected?.let { " | Actual value [$actual]" } ?: ""
val msg = ": $message $expectedText$actualText"
return task("Verification") {
return taskWithResult("Verification") {
ProvResult(
success,
cmd = if (success) msg else null,

View file

@ -489,7 +489,7 @@ internal class ProvTest {
addResultToEval(ProvResult(true))
}
fun Prov.outer() = task {
fun Prov.outer() = taskWithResult {
inner()
ProvResult(false)
}
@ -504,11 +504,11 @@ internal class ProvTest {
@Test
fun task_with_failing_subtask_and_successful_result_fails() {
// given
fun Prov.inner() = task {
fun Prov.inner() = taskWithResult {
ProvResult(false)
}
fun Prov.outer() = task {
fun Prov.outer() = taskWithResult {
inner()
ProvResult(true)
}
@ -527,7 +527,7 @@ internal class ProvTest {
addResultToEval(ProvResult(false))
}
fun Prov.outer() = task {
fun Prov.outer() = taskWithResult {
inner()
ProvResult(true)
}

View file

@ -29,7 +29,7 @@ internal class TaskFunctionsKtTest {
}
var count2 = 3
fun Prov.thirdTimeSuccess() = task {
fun Prov.thirdTimeSuccess() = taskWithResult {
if (count2 <= 1) {
count2 = 3
ProvResult(true, out = "1")