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", version: String = "1.12.7",
enforceVersion: Boolean = false, enforceVersion: Boolean = false,
sha256sum: String = "0824d5110ff1e68bff1ba10c1be63acb67cb1ad8e3bccddd6b6fc989608beca8" // checksum for sha256sum version 8.30 (e.g. ubuntu 20.04) sha256sum: String = "0824d5110ff1e68bff1ba10c1be63acb67cb1ad8e3bccddd6b6fc989608beca8" // checksum for sha256sum version 8.30 (e.g. ubuntu 20.04)
) = task { ) = taskWithResult {
if (isPackageInstalled("gopass") && !enforceVersion) { if (isPackageInstalled("gopass") && !enforceVersion) {
return@task ProvResult(true) return@taskWithResult ProvResult(true)
} }
if (checkGopassVersion(version)) { 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" 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 configFile = ".config/gopass/config.yml"
val defaultRootFolder = userHome() + ".password-store" val defaultRootFolder = userHome() + ".password-store"
val rootFolder = gopassRootFolder ?: defaultRootFolder val rootFolder = gopassRootFolder ?: defaultRootFolder
if (checkFile(configFile)) { 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("/"))) { 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 // use default
createDir(rootFolder) 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.install.base.aptInstall
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.whoami import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.whoami
fun Prov.installVirtualBoxGuestAdditions() = task { fun Prov.installVirtualBoxGuestAdditions() = taskWithResult {
// if running in a VirtualBox vm // if running in a VirtualBox vm
if (!chk("lspci | grep VirtualBox")) { 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")) { if (chk("VBoxService --version")) {
return@task ProvResult(true, "VBoxService already installed") return@taskWithResult ProvResult(true, "VBoxService already installed")
} }
// install guest additions // install guest additions

View file

@ -67,11 +67,19 @@ open class Prov protected constructor(
private var runInContainerWithName: String? = null private var runInContainerWithName: String? = null
/** /**
* Defines a task with a custom name instead of the name of the calling function. * 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 all subtasks finished with success (same as requireAll). * Returns success if no sub-tasks are called or if all subtasks finish with success.
*/ */
fun task(name: String? = null, a: Prov.() -> ProvResult): ProvResult { fun task(name: String? = null, taskLambda: Prov.() -> Unit): ProvResult {
return handle(ResultMode.ALL, name) { a() } 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 { fun requireLast(a: Prov.() -> ProvResult): ProvResult {
return handle(ResultMode.LAST) { a() } 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 * 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 { fun requireAll(a: Prov.() -> ProvResult): ProvResult {
return handle(ResultMode.ALL) { a() } return handle(ResultMode.ALL) { a() }
} }
@ -218,7 +226,7 @@ open class Prov protected constructor(
* Adds a ProvResult to the overall success evaluation. * Adds a ProvResult to the overall success evaluation.
* Intended for use in methods which do not automatically add results. * Intended for use in methods which do not automatically add results.
*/ */
fun addResultToEval(result: ProvResult) = task { fun addResultToEval(result: ProvResult) = taskWithResult {
result result
} }
@ -227,7 +235,7 @@ open class Prov protected constructor(
* Multi-line commands within the script are not supported. * 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. * 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 lines = script.trimIndent().replace("\\\n", "").replace("\r\n", "\n").split("\n")
val linesWithoutComments = lines.stream().map { it.split("#")[0] } val linesWithoutComments = lines.stream().map { it.split("#")[0] }
val linesNonEmpty = linesWithoutComments.filter { it.trim().isNotEmpty() } val linesNonEmpty = linesWithoutComments.filter { it.trim().isNotEmpty() }

View file

@ -18,7 +18,7 @@ import java.net.InetAddress
*/ */
internal fun getCallingMethodName(): String? { internal fun getCallingMethodName(): String? {
val offsetVal = 1 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 // suffixes are also ignored as method names but will be added as suffix in the evaluation results
val suffixes = arrayOf("optional", "requireAll", "requireLast", "inContainer") 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)) exec(SHELL, "-c", commandWithDirAndSudo(cmd, dir, sudo))
} }

View file

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

View file

@ -17,11 +17,11 @@ fun Prov.gitClone(
targetPath: String = "", targetPath: String = "",
pullIfExisting: Boolean = true, pullIfExisting: Boolean = true,
targetFolderName: String? = null targetFolderName: String? = null
): ProvResult = task { ): ProvResult = taskWithResult {
// if specified, use targetFolderName as basename or otherwise retrieve basename from repoSource // if specified, use targetFolderName as basename or otherwise retrieve basename from repoSource
val basename = targetFolderName ?: cmdNoEval("basename $repoSource .git").out?.trim() val basename = targetFolderName ?: cmdNoEval("basename $repoSource .git").out?.trim()
// return err if basename could not be retrieved from repoSource // 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 val pathWithBasename = targetPath.normalizePath() + basename
if (checkDir(pathWithBasename + "/.git/")) { 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, * @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 * 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 packageList = packages.split(" ")
val allInstalled: Boolean = packageList.map { isPackageInstalled(it) }.fold(true, { a, b -> a && b }) val allInstalled: Boolean = packageList.map { isPackageInstalled(it) }.fold(true, { a, b -> a && b })
if (!allInstalled) { 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. * 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. * 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)) { if (isHostKnown(host)) {
return@task ProvResult(true, out = "Host already known") return@taskWithResult ProvResult(true, out = "Host already known")
} }
if (!checkFile(KNOWN_HOSTS_FILE)) { if (!checkFile(KNOWN_HOSTS_FILE)) {
createDir(".ssh") 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 // logic based on https://serverfault.com/questions/447028/non-interactive-git-clone-ssh-fingerprint-prompt
val actualKeys = findSshKeys(host) val actualKeys = findSshKeys(host)
if (actualKeys == null || actualKeys.size == 0) { 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) val actualFingerprints = getFingerprintsForKeys(actualKeys)
for (fingerprintToBeAdded in fingerprintsOfKeysToBeAdded) { for (fingerprintToBeAdded in fingerprintsOfKeysToBeAdded) {
@ -67,7 +67,7 @@ fun Prov.trustHost(host: String, fingerprintsOfKeysToBeAdded: Set<String>?) = ta
} }
} }
if (indexOfKeyFound == -1) { if (indexOfKeyFound == -1) {
return@task ProvResult( return@taskWithResult ProvResult(
false, false,
err = "Fingerprint ($fingerprintToBeAdded) could not be found in actual fingerprints: $actualFingerprints" err = "Fingerprint ($fingerprintToBeAdded) could not be found in actual fingerprints: $actualFingerprints"
) )

View file

@ -22,13 +22,13 @@ fun Prov.downloadFromURL(
followRedirect: Boolean = true, followRedirect: Boolean = true,
sha256sum: String? = null, sha256sum: String? = null,
overwrite: Boolean = false overwrite: Boolean = false
): ProvResult = task { ): ProvResult = taskWithResult {
val finalFilename: String = filename ?: url.substringAfterLast("/") val finalFilename: String = filename ?: url.substringAfterLast("/")
val fqFilename: String = (path?.normalizePath() ?: "") + finalFilename val fqFilename: String = (path?.normalizePath() ?: "") + finalFilename
if (!overwrite && checkFile(fqFilename, sudo = sudo)) { 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") 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()) { 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) createDirs(k8sCredentialsDir, sudo = true)

View file

@ -8,17 +8,17 @@ import org.domaindrivenarchitecture.provs.syspec.infrastructure.findSpecConfigFr
import org.domaindrivenarchitecture.provs.syspec.infrastructure.verifySpecConfig 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 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) verifySpecConfig(spec)
} }
@Suppress("unused") // Api @Suppress("unused") // Api
fun Prov.verifySpecFromResource(resourceName: String) = task { fun Prov.verifySpecFromResource(resourceName: String) = taskWithResult {
val result = findSpecConfigFromResource(resourceName) 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) 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 actualText = expected?.let { " | Actual value [$actual]" } ?: ""
val msg = ": $message $expectedText$actualText" val msg = ": $message $expectedText$actualText"
return task("Verification") { return taskWithResult("Verification") {
ProvResult( ProvResult(
success, success,
cmd = if (success) msg else null, cmd = if (success) msg else null,

View file

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

View file

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