introduce method session &refactor Application.kt
This commit is contained in:
parent
c78cf8e3bf
commit
eba6037fcc
6 changed files with 108 additions and 29 deletions
|
@ -2,8 +2,11 @@ package org.domaindrivenarchitecture.provs.desktop.application
|
||||||
|
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import org.domaindrivenarchitecture.provs.configuration.application.ensureSudoWithoutPassword
|
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.domain.provisionDesktopCommand
|
||||||
|
import org.domaindrivenarchitecture.provs.desktop.infrastructure.getConfig
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.cli.createProvInstance
|
import org.domaindrivenarchitecture.provs.framework.core.cli.createProvInstance
|
||||||
|
import org.domaindrivenarchitecture.provs.framework.core.cli.quit
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
@ -18,22 +21,33 @@ fun main(args: Array<String>) {
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val config = if (cmd.configFile == null) DesktopConfig() else
|
||||||
|
try {
|
||||||
|
getConfig(cmd.configFile.fileName)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
println(
|
||||||
|
"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."
|
||||||
|
)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config == null) {
|
||||||
|
println("No suitable config found.")
|
||||||
|
quit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
val prov = createProvInstance(cmd.target)
|
val prov = createProvInstance(cmd.target)
|
||||||
|
|
||||||
try {
|
prov.session {
|
||||||
prov.task {
|
ensureSudoWithoutPassword(cmd.target.remoteTarget()?.password)
|
||||||
ensureSudoWithoutPassword(cmd.target.remoteTarget()?.password)
|
provisionDesktopCommand(cmd, config)
|
||||||
provisionDesktopCommand(cmd)
|
|
||||||
}
|
|
||||||
} catch (e: SerializationException) {
|
|
||||||
println(
|
|
||||||
"Error: File \"${cmd.configFile?.fileName}\" has an invalid format and or invalid data.\n"
|
|
||||||
)
|
|
||||||
} 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"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.currentUserCanSudoWithoutPassword
|
||||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.whoami
|
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.whoami
|
||||||
|
|
||||||
internal fun Prov.provisionDesktopCommand(cmd: DesktopCliCommand) = task {
|
internal fun Prov.provisionDesktopCommand(cmd: DesktopCliCommand, conf: DesktopConfig) = task {
|
||||||
|
|
||||||
// retrieve config
|
|
||||||
val conf = if (cmd.configFile != null) getConfig(cmd.configFile.fileName) else DesktopConfig()
|
|
||||||
|
|
||||||
provisionDesktop(
|
provisionDesktop(
|
||||||
cmd.type,
|
cmd.type,
|
||||||
conf.ssh?.keyPair(),
|
conf.ssh?.keyPair(),
|
||||||
|
|
|
@ -68,6 +68,17 @@ open class Prov protected constructor(
|
||||||
private val internalResults = arrayListOf<ResultLine>()
|
private val internalResults = arrayListOf<ResultLine>()
|
||||||
private val infoTexts = arrayListOf<String>()
|
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).
|
* 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.
|
* Returns success if no sub-tasks are called or if all subtasks finish with success.
|
||||||
|
|
|
@ -52,3 +52,11 @@ internal fun createRemoteProvInstance(
|
||||||
internal fun getPasswordToConfigureSudoWithoutPassword(): Secret {
|
internal fun getPasswordToConfigureSudoWithoutPassword(): Secret {
|
||||||
return PromptSecretSource("password to configure sudo without password.").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)
|
||||||
|
}
|
|
@ -4,16 +4,21 @@ import ch.qos.logback.classic.Level
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
|
||||||
import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
|
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.desktop.infrastructure.getConfig
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.*
|
import org.domaindrivenarchitecture.provs.framework.core.*
|
||||||
import org.domaindrivenarchitecture.provs.framework.core.cli.getPasswordToConfigureSudoWithoutPassword
|
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.framework.core.processors.DummyProcessor
|
||||||
import org.domaindrivenarchitecture.provs.test.setRootLoggingLevel
|
import org.domaindrivenarchitecture.provs.test.setRootLoggingLevel
|
||||||
import org.junit.jupiter.api.AfterAll
|
import org.junit.jupiter.api.AfterAll
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.BeforeAll
|
import org.junit.jupiter.api.BeforeAll
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
|
|
||||||
|
@ -91,6 +96,9 @@ internal class ApplicationKtTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun prints_error_message_if_config_not_found() {
|
fun prints_error_message_if_config_not_found() {
|
||||||
|
mockkStatic(::quit)
|
||||||
|
every { quit(any()) } throws RuntimeException("mockked")
|
||||||
|
|
||||||
// given
|
// given
|
||||||
setRootLoggingLevel(Level.OFF)
|
setRootLoggingLevel(Level.OFF)
|
||||||
|
|
||||||
|
@ -103,21 +111,28 @@ internal class ApplicationKtTest {
|
||||||
System.setErr(PrintStream(errContent))
|
System.setErr(PrintStream(errContent))
|
||||||
|
|
||||||
// when
|
// when
|
||||||
main(arrayOf("basic", "someuser@remotehost", "-c", "idontexist.yaml"))
|
assertThrows<RuntimeException> {
|
||||||
|
main(arrayOf("basic", "someuser@remotehost", "-c", "idontexist.yaml"))
|
||||||
|
}
|
||||||
|
|
||||||
// then
|
// then
|
||||||
System.setOut(originalOut)
|
System.setOut(originalOut)
|
||||||
System.setErr(originalErr)
|
System.setErr(originalErr)
|
||||||
|
|
||||||
val expectedOutput =
|
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", ""))
|
assertEquals(expectedOutput, outContent.toString().replace("\r", "").replace("\n", ""))
|
||||||
|
|
||||||
verify(exactly = 0) { any<Prov>().provisionDesktop(any(), any(), any(), any(), any(), any()) }
|
verify(exactly = 0) { any<Prov>().provisionDesktop(any(), any(), any(), any(), any(), any()) }
|
||||||
|
|
||||||
|
unmockkStatic(::quit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun prints_error_message_if_config_not_parsable() {
|
fun prints_error_message_if_config_not_parsable() {
|
||||||
|
mockkStatic(::quit)
|
||||||
|
every { quit(any()) } throws RuntimeException("mockked")
|
||||||
|
|
||||||
// given
|
// given
|
||||||
setRootLoggingLevel(Level.OFF)
|
setRootLoggingLevel(Level.OFF)
|
||||||
|
|
||||||
|
@ -130,16 +145,20 @@ internal class ApplicationKtTest {
|
||||||
System.setErr(PrintStream(errContent))
|
System.setErr(PrintStream(errContent))
|
||||||
|
|
||||||
// when
|
// when
|
||||||
main(arrayOf("basic", "someuser@remotehost", "-c", "src/test/resources/invalid-desktop-config.yaml"))
|
assertThrows<RuntimeException> {
|
||||||
|
main(arrayOf("basic", "someuser@remotehost", "-c", "src/test/resources/invalid-desktop-config.yaml"))
|
||||||
|
}
|
||||||
|
|
||||||
// then
|
// then
|
||||||
System.setOut(originalOut)
|
System.setOut(originalOut)
|
||||||
System.setErr(originalErr)
|
System.setErr(originalErr)
|
||||||
|
|
||||||
val expectedOutput =
|
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", ""))
|
assertEquals(expectedOutput, outContent.toString().replace("\r", "").replace("\n", ""))
|
||||||
|
|
||||||
verify(exactly = 0) { any<Prov>().provisionDesktop(any(), any(), any(), any(), any(), any()) }
|
verify(exactly = 0) { any<Prov>().provisionDesktop(any(), any(), any(), any(), any(), any()) }
|
||||||
|
|
||||||
|
unmockkStatic(::quit)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -658,7 +658,12 @@ internal class ProvTest {
|
||||||
}
|
}
|
||||||
optional("sub2c-optional") {
|
optional("sub2c-optional") {
|
||||||
taskWithResult("sub3a-taskWithResult") {
|
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") {
|
requireLast("sub2d-requireLast") {
|
||||||
|
@ -668,10 +673,16 @@ internal class ProvTest {
|
||||||
}
|
}
|
||||||
task("sub2e-task") {
|
task("sub2e-task") {
|
||||||
addResultToEval(ProvResult(true))
|
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") {
|
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 ")
|
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", ""))
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue