diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/core/Prov.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/core/Prov.kt index 6d8372e..01df56e 100644 --- a/src/main/kotlin/org/domaindrivenarchitecture/provs/core/Prov.kt +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/core/Prov.kt @@ -285,8 +285,15 @@ open class Prov protected constructor( // post-handling val returnValue = if (mode == ResultMode.LAST) { - if (internalResultIsLeaf(resultIndex) || taskName == "cmd") - res.copy() else ProvResult(res.success) + if (internalResultIsLeaf(resultIndex) || taskName == "cmd" || taskName?.replace(" (requireLast)", "") == "repeatTaskUntilSuccess") { // todo: improve and remove replace function + // for a leaf (task with mo subtask) or tasks "cmd" resp. "repeatUntilTrue" provide also out and err of original results + // because results of cmd and leafs are not included in the reporting + // and the caller of repeatUntilTrue might need the complete result (incl. out and err) and not only success value + res.copy() + } else { + // just pass success value, no other data of the original result + ProvResult(res.success) + } } else if (mode == ResultMode.ALL) { // leaf if (internalResultIsLeaf(resultIndex)) res.copy() diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/core/TaskFunctions.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/core/TaskFunctions.kt new file mode 100644 index 0000000..bdcfc64 --- /dev/null +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/core/TaskFunctions.kt @@ -0,0 +1,17 @@ +package org.domaindrivenarchitecture.provs.core + + +/** + * Repeats task until it returns success + */ +fun Prov.repeatTaskUntilSuccess(times: Int, sleepInSec: Int, func: Prov.() -> ProvResult) = requireLast { + require(times > 0) + var result = ProvResult(false, err = "Internal error") // Will only be returned if function is not executed at all, otherwise func's last result is returned + for (i in 1..times) { + result = func() + if (result.success) + return@requireLast result + Thread.sleep(sleepInSec * 1000L) + } + result +} diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/core/Utils.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/core/Utils.kt index ded3d4c..dd80e23 100644 --- a/src/main/kotlin/org/domaindrivenarchitecture/provs/core/Utils.kt +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/core/Utils.kt @@ -8,20 +8,6 @@ import org.domaindrivenarchitecture.provs.core.tags.Api import java.io.File import java.net.InetAddress -/** - * Repeats task until it returns success - */ -fun Prov.repeatTask(times: Int, sleepInSec: Int, func: Prov.() -> ProvResult) = requireLast { - require(times > 0) - var result = ProvResult(false, err = "Internal error") // Will only be returned if function is not executed at all, otherwise func's result is returned - for (i in 1..times) { - result = func() - if (result.success) - return@requireLast result - Thread.sleep(sleepInSec * 1000L) - } - return@requireLast result -} /** * Returns the name of the calling function but excluding some functions of the prov framework @@ -29,7 +15,7 @@ fun Prov.repeatTask(times: Int, sleepInSec: Int, func: Prov.() -> ProvResult) = * 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? { +internal fun getCallingMethodName(): String? { val offsetVal = 1 val exclude = arrayOf("task", "def", "record", "invoke", "invoke0", "handle", "task\$default", "def\$default", "addResultToEval", "handle\$default") // suffixes are also ignored as method names but will be added as suffix in the evaluation results @@ -55,6 +41,7 @@ fun getCallingMethodName(): String? { } +// --------------------------- String extensions ---------------------------- fun String.escapeNewline(): String = replace("\r", "\\r").replace("\n", "\\n") fun String.escapeControlChars(): String = replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t").replace("[\\p{Cntrl}]".toRegex(), "\\?") fun String.escapeBackslash(): String = replace("\\", "\\\\") @@ -66,7 +53,6 @@ fun String.escapeSingleQuoteForShell(): String = replace("'", "'\"'\"'") fun String.escapeProcentForPrintf(): String = replace("%", "%%") fun String.endingWithFileSeparator(): String = if (length > 0 && (last() != fileSeparatorChar())) this + fileSeparator() else this - /** * Put String between double quotes and escapes chars that need to be escaped (by backslash) for use in Unix Shell String * I.e. the following chars are escaped: backslash, backtick, double quote, dollar @@ -79,6 +65,7 @@ fun String.escapeForShell(): String { return this.escapeBackslash().escapeBacktick().escapeDoubleQuote().escapeDollar() } + /** * Returns an echo command for the given String, which will be escaped for the bash */ @@ -162,6 +149,10 @@ fun docker( } +/** + * Returns true if sudo is required to run docker locally, otherwise returns false. + * Throws an IllegalStateException if docker cannot be run locally at all. + */ fun checkSudoRequiredForDocker(): Boolean { return if (local().chk("docker -v")) { false diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/server_software/k3s/domain/K3d.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/server_software/k3s/domain/K3d.kt index 93a9bf4..4a43096 100644 --- a/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/server_software/k3s/domain/K3d.kt +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/server_software/k3s/domain/K3d.kt @@ -3,16 +3,20 @@ package org.domaindrivenarchitecture.provs.extensions.server_software.k3s.domain import org.domaindrivenarchitecture.provs.core.Prov import org.domaindrivenarchitecture.provs.core.docker.provideContainer import org.domaindrivenarchitecture.provs.core.echoCommandForTextWithNewlinesReplaced -import org.domaindrivenarchitecture.provs.core.repeatTask +import org.domaindrivenarchitecture.provs.core.repeatTaskUntilSuccess /** * Runs a k3s server and a k3s agent as containers. + * Copies the kubeconfig from container to the default location: $HOME/.kube/config */ fun Prov.installK3sAsContainers(token: String = "12345678901234") = task { cmd("docker volume create k3s-server") - provideContainer("k3s-server", "rancher/k3s", command = "server --cluster-init", options = "-d --privileged --tmpfs /run --tmpfs /var/run -e K3S_TOKEN=$token -e K3S_KUBECONFIG_OUTPUT=./kubeconfig.yaml -e K3S_KUBECONFIG_MODE=666 -v k3s-server:/var/lib/rancher/k3s:z -p 6443:6443 -p 80:80 -p 443:443") - provideContainer("k3s-agent", "rancher/k3s", options = "-d --privileged --tmpfs /run --tmpfs /var/run -e K3S_TOKEN=$token -e K3S_URL=https://server:6443") + provideContainer("k3s-server", "rancher/k3s", command = "server --cluster-init", options = + "-d --privileged --tmpfs /run --tmpfs /var/run " + + "-e K3S_TOKEN=$token -e K3S_KUBECONFIG_OUTPUT=./kubeconfig.yaml -e K3S_KUBECONFIG_MODE=666 " + + "-v k3s-server:/var/lib/rancher/k3s:z -p 6443:6443 -p 80:80 -p 443:443 " + + "--ulimit nproc=65535 --ulimit nofile=65535:65535") // wait for config file cmd("export timeout=60; while [ ! -f /var/lib/docker/volumes/k3s-server/_data/server/kubeconfig.yaml ]; do if [ \"${'$'}timeout\" == 0 ]; then echo \"ERROR: Timeout while waiting for file.\"; break; fi; sleep 1; ((timeout--)); done") @@ -29,7 +33,7 @@ fun Prov.installK3sAsContainers(token: String = "12345678901234") = task { * Prerequisite: Kubectl has to be installed */ fun Prov.applyK8sConfig(configAsYaml: String, kubectlCommand: String = "kubectl") = task { - repeatTask(6, 10) { + repeatTaskUntilSuccess(6, 10) { cmd(echoCommandForTextWithNewlinesReplaced(configAsYaml) + " | $kubectlCommand apply -f -") } } diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/server_software/k3s/infrastructure/apple/Apple.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/server_software/k3s/infrastructure/apple/Apple.kt index ff9f02f..efe7b09 100644 --- a/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/server_software/k3s/infrastructure/apple/Apple.kt +++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/server_software/k3s/infrastructure/apple/Apple.kt @@ -2,7 +2,7 @@ package org.domaindrivenarchitecture.provs.extensions.server_software.k3s.infras import org.domaindrivenarchitecture.provs.core.Prov import org.domaindrivenarchitecture.provs.core.ProvResult -import org.domaindrivenarchitecture.provs.core.repeatTask +import org.domaindrivenarchitecture.provs.core.repeatTaskUntilSuccess /** @@ -10,14 +10,14 @@ import org.domaindrivenarchitecture.provs.core.repeatTask */ fun Prov.checkAppleService(host: String = "127.0.0.1") = requireLast { // repeat required as curl may return with "empty reply from server" or with "Recv failure: Connection reset by peer" - val res = repeatTask(12, 10) { + val res = repeatTaskUntilSuccess(12, 10) { cmd("curl -m 30 $host/apple") }.out?.trim() - return@requireLast if ("apple" == res) { + if ("apple" == res) { ProvResult(true, out = res) } else { - ProvResult(false,err = "Url $host/apple did not return text \"apple\" but returned: $res") + ProvResult(false, err = "Url $host/apple did not return text \"apple\" but returned: $res") } } diff --git a/src/test/kotlin/org/domaindrivenarchitecture/provs/core/ProvTest.kt b/src/test/kotlin/org/domaindrivenarchitecture/provs/core/ProvTest.kt index 2fd3548..7dfa67c 100644 --- a/src/test/kotlin/org/domaindrivenarchitecture/provs/core/ProvTest.kt +++ b/src/test/kotlin/org/domaindrivenarchitecture/provs/core/ProvTest.kt @@ -289,7 +289,8 @@ internal class ProvTest { System.setErr(PrintStream(errContent)) // when - Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.NONE).methodThatProvidesSomeOutput() + Prov.newInstance(name = "test instance with no progress info", progressType = ProgressType.NONE) + .methodThatProvidesSomeOutput() // then System.setOut(originalOut) @@ -298,18 +299,18 @@ internal class ProvTest { println(outContent.toString()) val expectedOutput = - "============================================== 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" + "============================================== SUMMARY (test instance with no progress info) ============================================== \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" assertEquals(expectedOutput, outContent.toString().replace("\r", "")) } @@ -465,4 +466,6 @@ internal class ProvTest { // then assertEquals(true, res.success) } + } + diff --git a/src/test/kotlin/org/domaindrivenarchitecture/provs/core/TaskFunctionsKtTest.kt b/src/test/kotlin/org/domaindrivenarchitecture/provs/core/TaskFunctionsKtTest.kt new file mode 100644 index 0000000..e4037fe --- /dev/null +++ b/src/test/kotlin/org/domaindrivenarchitecture/provs/core/TaskFunctionsKtTest.kt @@ -0,0 +1,37 @@ +package org.domaindrivenarchitecture.provs.core + +import org.domaindrivenarchitecture.provs.test.testLocal +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +internal class TaskFunctionsKtTest { + + var count = 1 + fun Prov.altenateSuccessAndFailure() = task { + if (count == 0) { + count = 1 + ProvResult(true, out = "0") + } else { + count-- + ProvResult(false, err = "1") + } + } + + fun Prov.repeating() = requireLast { + val res = repeatTaskUntilSuccess(4, 1) { + altenateSuccessAndFailure() + } + + if (res.success && ("0" == res.out?.trim())) { + ProvResult(true) + } else { + ProvResult(false) + } + } + + + @Test + fun repeat_and_requireLast() { + assertTrue(testLocal().repeating().success) + } +} \ No newline at end of file diff --git a/src/test/kotlin/org/domaindrivenarchitecture/provs/extensions/server_software/k3s/domain/K3dKtTest.kt b/src/test/kotlin/org/domaindrivenarchitecture/provs/extensions/server_software/k3s/domain/K3dKtTest.kt index e0aafa8..d2297c9 100644 --- a/src/test/kotlin/org/domaindrivenarchitecture/provs/extensions/server_software/k3s/domain/K3dKtTest.kt +++ b/src/test/kotlin/org/domaindrivenarchitecture/provs/extensions/server_software/k3s/domain/K3dKtTest.kt @@ -10,21 +10,24 @@ import org.domaindrivenarchitecture.provs.extensions.server_software.k3s.infrast import org.domaindrivenarchitecture.provs.test.tags.ContainerTest import org.domaindrivenarchitecture.provs.test.tags.NonCi import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test internal class K3dKtTest { @Test + @Disabled // remove line and execute manually as this test may take several minutes @ContainerTest @NonCi fun installK3sAsContainers() { + // given val containerName = "alpine-docker-dind" local().task { provideContainer( containerName, "yobasystems/alpine-docker:dind-amd64", - // ContainerStartMode.CREATE_NEW_KILL_EXISTING, // for re-create a potentially existing container + ContainerStartMode.CREATE_NEW_KILL_EXISTING, // for re-create a potentially existing container sudo = false, options = "--privileged" ) @@ -35,7 +38,6 @@ internal class K3dKtTest { val result = docker(containerName, sudo = false).task { - // given cmd("apk update") cmd("apk add sudo curl") task( @@ -49,9 +51,9 @@ internal class K3dKtTest { """.trimIndent()) } + // when installK3sAsContainers() - applyK8sConfig(appleConfig()) cmd("kubectl wait --for=condition=ready --timeout=600s pod apple-app")