introduce method session &refactor Application.kt

This commit is contained in:
az 2023-04-13 18:22:17 +02:00
parent c78cf8e3bf
commit eba6037fcc
6 changed files with 108 additions and 29 deletions

View file

@ -2,8 +2,11 @@ package org.domaindrivenarchitecture.provs.desktop.application
import kotlinx.serialization.SerializationException
import org.domaindrivenarchitecture.provs.configuration.application.ensureSudoWithoutPassword
import org.domaindrivenarchitecture.provs.desktop.domain.DesktopConfig
import org.domaindrivenarchitecture.provs.desktop.domain.provisionDesktopCommand
import org.domaindrivenarchitecture.provs.desktop.infrastructure.getConfig
import org.domaindrivenarchitecture.provs.framework.core.cli.createProvInstance
import org.domaindrivenarchitecture.provs.framework.core.cli.quit
import java.io.FileNotFoundException
import kotlin.system.exitProcess
@ -18,22 +21,33 @@ fun main(args: Array<String>) {
exitProcess(1)
}
val prov = createProvInstance(cmd.target)
val config = if (cmd.configFile == null) DesktopConfig() else
try {
prov.task {
ensureSudoWithoutPassword(cmd.target.remoteTarget()?.password)
provisionDesktopCommand(cmd)
}
getConfig(cmd.configFile.fileName)
} catch (e: SerializationException) {
println(
"Error: File \"${cmd.configFile?.fileName}\" has an invalid format and or invalid data.\n"
"Error: File \"${cmd.configFile.fileName}\" has an invalid format and or invalid data."
)
null
} catch (e: FileNotFoundException) {
println(
"Error: File\u001b[31m ${cmd.configFile?.fileName} \u001b[0m was not found.\n" +
"Pls copy file \u001B[31m desktop-config-example.yaml \u001B[0m to file \u001B[31m ${cmd.configFile?.fileName} \u001B[0m " +
"and change the content according to your needs.\n"
"Error: File\u001b[31m ${cmd.configFile.fileName} \u001b[0m was not found.\n" +
"Pls copy file \u001B[31m desktop-config-example.yaml \u001B[0m to file \u001B[31m ${cmd.configFile.fileName} \u001B[0m " +
"and change the content according to your needs."
)
null
}
if (config == null) {
println("No suitable config found.")
quit(-1)
}
val prov = createProvInstance(cmd.target)
prov.session {
ensureSudoWithoutPassword(cmd.target.remoteTarget()?.password)
provisionDesktopCommand(cmd, config)
}
}

View file

@ -12,11 +12,7 @@ import org.domaindrivenarchitecture.provs.framework.ubuntu.keys.provisionKeys
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.currentUserCanSudoWithoutPassword
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.whoami
internal fun Prov.provisionDesktopCommand(cmd: DesktopCliCommand) = task {
// retrieve config
val conf = if (cmd.configFile != null) getConfig(cmd.configFile.fileName) else DesktopConfig()
internal fun Prov.provisionDesktopCommand(cmd: DesktopCliCommand, conf: DesktopConfig) = task {
provisionDesktop(
cmd.type,
conf.ssh?.keyPair(),

View file

@ -68,6 +68,17 @@ open class Prov protected constructor(
private val internalResults = arrayListOf<ResultLine>()
private val infoTexts = arrayListOf<String>()
/**
* A session is the top-level execution unit in provs. A session can contain tasks.
* Returns success if no sub-tasks are called or if all subtasks finish with success.
*/
fun session(taskLambda: Prov.() -> Unit) = task("session") {
if (level > 1) {
throw IllegalStateException("A session can only be created on the top-level and may not be included in another session or task.")
}
taskLambda()
}
/**
* A task is the base execution unit in provs. In the results overview it is represented by one line resp. result (of either success or failure).
* Returns success if no sub-tasks are called or if all subtasks finish with success.

View file

@ -52,3 +52,11 @@ internal fun createRemoteProvInstance(
internal fun getPasswordToConfigureSudoWithoutPassword(): Secret {
return PromptSecretSource("password to configure sudo without password.").secret()
}
/**
* Wrapper for exitProcess, which allows e.g. mocking for test purposes
*/
fun quit(status: Int): Nothing {
exitProcess(status)
}

View file

@ -4,16 +4,21 @@ import ch.qos.logback.classic.Level
import io.mockk.*
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
import org.domaindrivenarchitecture.provs.desktop.domain.*
import org.domaindrivenarchitecture.provs.desktop.domain.DesktopCliCommand
import org.domaindrivenarchitecture.provs.desktop.domain.DesktopConfig
import org.domaindrivenarchitecture.provs.desktop.domain.DesktopType
import org.domaindrivenarchitecture.provs.desktop.domain.provisionDesktop
import org.domaindrivenarchitecture.provs.desktop.infrastructure.getConfig
import org.domaindrivenarchitecture.provs.framework.core.*
import org.domaindrivenarchitecture.provs.framework.core.cli.getPasswordToConfigureSudoWithoutPassword
import org.domaindrivenarchitecture.provs.framework.core.cli.quit
import org.domaindrivenarchitecture.provs.framework.core.processors.DummyProcessor
import org.domaindrivenarchitecture.provs.test.setRootLoggingLevel
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.io.ByteArrayOutputStream
import java.io.PrintStream
@ -91,6 +96,9 @@ internal class ApplicationKtTest {
@Test
fun prints_error_message_if_config_not_found() {
mockkStatic(::quit)
every { quit(any()) } throws RuntimeException("mockked")
// given
setRootLoggingLevel(Level.OFF)
@ -103,21 +111,28 @@ internal class ApplicationKtTest {
System.setErr(PrintStream(errContent))
// when
assertThrows<RuntimeException> {
main(arrayOf("basic", "someuser@remotehost", "-c", "idontexist.yaml"))
}
// then
System.setOut(originalOut)
System.setErr(originalErr)
val expectedOutput =
"Error: File\u001B[31m idontexist.yaml \u001B[0m was not found.Pls copy file \u001B[31m desktop-config-example.yaml \u001B[0m to file \u001B[31m idontexist.yaml \u001B[0m and change the content according to your needs."
"Error: File\u001B[31m idontexist.yaml \u001B[0m was not found.Pls copy file \u001B[31m desktop-config-example.yaml \u001B[0m to file \u001B[31m idontexist.yaml \u001B[0m and change the content according to your needs.No suitable config found."
assertEquals(expectedOutput, outContent.toString().replace("\r", "").replace("\n", ""))
verify(exactly = 0) { any<Prov>().provisionDesktop(any(), any(), any(), any(), any(), any()) }
unmockkStatic(::quit)
}
@Test
fun prints_error_message_if_config_not_parsable() {
mockkStatic(::quit)
every { quit(any()) } throws RuntimeException("mockked")
// given
setRootLoggingLevel(Level.OFF)
@ -130,16 +145,20 @@ internal class ApplicationKtTest {
System.setErr(PrintStream(errContent))
// when
assertThrows<RuntimeException> {
main(arrayOf("basic", "someuser@remotehost", "-c", "src/test/resources/invalid-desktop-config.yaml"))
}
// then
System.setOut(originalOut)
System.setErr(originalErr)
val expectedOutput =
"Error: File \"src/test/resources/invalid-desktop-config.yaml\" has an invalid format and or invalid data."
"Error: File \"src/test/resources/invalid-desktop-config.yaml\" has an invalid format and or invalid data.No suitable config found."
assertEquals(expectedOutput, outContent.toString().replace("\r", "").replace("\n", ""))
verify(exactly = 0) { any<Prov>().provisionDesktop(any(), any(), any(), any(), any(), any()) }
unmockkStatic(::quit)
}
}

View file

@ -658,7 +658,12 @@ internal class ProvTest {
}
optional("sub2c-optional") {
taskWithResult("sub3a-taskWithResult") {
addResultToEval(ProvResult(false, err = "returned-result - error msg B should be once in output - in addResultToEval"))
addResultToEval(
ProvResult(
false,
err = "returned-result - error msg B should be once in output - in addResultToEval"
)
)
}
}
requireLast("sub2d-requireLast") {
@ -668,10 +673,16 @@ internal class ProvTest {
}
task("sub2e-task") {
addResultToEval(ProvResult(true))
ProvResult(false, err = "error should NOT be in output as results of task (not taskWithResult) are ignored")
ProvResult(
false,
err = "error should NOT be in output as results of task (not taskWithResult) are ignored"
)
}
taskWithResult("sub2f-taskWithResult") {
ProvResult(false, err = "returned-result - error msg C should be once in output - at the end of sub3taskWithResult ")
ProvResult(
false,
err = "returned-result - error msg C should be once in output - at the end of sub3taskWithResult "
)
}
ProvResult(false, err = "returned-result - error msg D should be once in output - at the end of sub1 ")
}
@ -725,5 +736,25 @@ internal class ProvTest {
assertEquals(expectedOutput, outContent.toString().replace("\r", ""))
}
}
@Test
fun session_on_top_level_succeeds() {
// when
val result = Prov.newInstance().session { cmd("echo bla") }
// then
assertTrue(result.success)
}
@Test
fun session_not_on_top_level_throws_an_exception() {
// when
val exception = org.junit.jupiter.api.assertThrows<RuntimeException> {
local().session {
session {
cmd("echo bla")
}
}
}
// then
assertEquals("A session can only be created on the top-level and may not be included in another session or task.", exception.message)
}
}