change cli to new format

This commit is contained in:
ansgarz 2022-02-24 19:21:18 +01:00
parent 4c6d3ba3c3
commit 478f058fd7
11 changed files with 110 additions and 135 deletions

View file

@ -6,49 +6,24 @@ import kotlinx.cli.default
import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
open class CliTargetParser(name: String) : ArgParser(name) {
val remoteHost by option(
ArgType.String, shortName =
"r", description = "provision to remote host - either localHost or remoteHost must be specified"
)
val localHost by option(
ArgType.Boolean, shortName =
"l", description = "provision to local machine - either localHost or remoteHost must be specified"
)
val userName by option(
val target by argument(
ArgType.String,
shortName = "u",
description = "user for remote provisioning."
description = "target: either 'local' or remote with 'user[:password]@host' (e.g. 'username@somehost.com' without password for ssh-key authorization)",
)
val sshWithGopassPath by option(
ArgType.String,
shortName = "p",
description = "password stored at gopass path"
)
val sshWithPasswordPrompt by option(
val passwordInteractive by option(
ArgType.Boolean,
shortName = "i",
description = "prompt for password interactive"
).default(false)
val sshWithKey by option(
ArgType.Boolean,
shortName = "k",
description = "provision over ssh using user & ssh key"
"password-interactive",
"p",
"prompt for password",
).default(false)
}
fun parseTarget(
programName: String = "java -jar provs.jar",
programName: String = "provs",
args: Array<String>
): TargetCliCommand {
val parser = CliTargetParser(programName)
parser.parse(args)
return TargetCliCommand(
parser.localHost,
parser.remoteHost,
parser.userName,
parser.sshWithPasswordPrompt,
parser.sshWithGopassPath,
parser.sshWithKey
)
return TargetCliCommand(parser.target, parser.passwordInteractive)
}

View file

@ -1,29 +1,64 @@
package org.domaindrivenarchitecture.provs.configuration.domain
import org.domaindrivenarchitecture.provs.framework.core.Secret
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.PlainSecretSource
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.PromptSecretSource
private const val USER_HOST_DELIMITER = "@"
private const val USER_PW_DELIMITER = ":"
class TargetCliCommand(
val localHost: Boolean?,
val remoteHost: String?,
val userName: String?,
val sshWithPasswordPrompt: Boolean,
val sshWithGopassPath: String?,
val sshWithKey: Boolean
val target: String,
val passwordInteractive: Boolean = false
) {
fun isValidLocalhost(): Boolean {
return (localHost ?: false) && remoteHost == null && userName == null && sshWithGopassPath == null &&
!sshWithPasswordPrompt && !sshWithKey
private var remoteTarget: RemoteTarget? = null
init {
remoteTarget = parseRemoteTarget()
}
fun hasValidPasswordOption(): Boolean {
return (sshWithGopassPath != null) xor sshWithPasswordPrompt xor sshWithKey
fun isValidLocalhost(): Boolean {
return target == "local"
}
fun isValidRemote(): Boolean {
return remoteHost != null && userName != null && hasValidPasswordOption()
return (remoteTarget != null)
}
fun isValid(): Boolean {
return (isValidLocalhost() || isValidRemote())
}
private fun parseRemoteTarget(): RemoteTarget? {
val user: String?
val host: String?
var password: Secret? = null
if (!target.contains(USER_HOST_DELIMITER)) {
return null
}
host = target.substringAfter(USER_HOST_DELIMITER)
val userPw = target.substringBefore(USER_HOST_DELIMITER)
if (!userPw.contains(USER_PW_DELIMITER)) {
user = userPw
} else {
user = userPw.substringBefore(USER_PW_DELIMITER)
password = PlainSecretSource(userPw.substringAfter(USER_PW_DELIMITER)).secret()
}
if (passwordInteractive) {
password = PromptSecretSource("Password for $user on $host").secretNullable()
}
return RemoteTarget(user, host, password)
}
fun remoteTarget(): RemoteTarget? {
return remoteTarget
}
class RemoteTarget(val user: String, val host: String, val password: Secret?)
}

View file

@ -25,12 +25,8 @@ open class CliArgumentsParser(name: String) : CliTargetParser(name) {
return DesktopCliCommand(
DesktopType.valueOf(module.name.uppercase()),
TargetCliCommand(
localHost,
remoteHost,
userName,
sshWithPasswordPrompt,
sshWithGopassPath,
sshWithKey
target,
passwordInteractive
),
module.configFileName
)

View file

@ -5,7 +5,6 @@ import org.domaindrivenarchitecture.provs.framework.core.Prov
import org.domaindrivenarchitecture.provs.framework.core.Secret
import org.domaindrivenarchitecture.provs.framework.core.local
import org.domaindrivenarchitecture.provs.framework.core.remote
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.GopassSecretSource
import org.domaindrivenarchitecture.provs.framework.ubuntu.secret.secretSources.PromptSecretSource
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.currentUserCanSudo
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.makeUserSudoerWithNoSudoPasswordRequired
@ -26,15 +25,16 @@ fun createProvInstance(
remoteHostSetSudoWithoutPasswordRequired: Boolean = false
): Prov {
if (targetCommand.isValid()) {
val password: Secret? = if (targetCommand.isValidRemote()) retrievePassword(targetCommand) else null
val password: Secret? = targetCommand.remoteTarget()?.password
val remoteTarget = targetCommand.remoteTarget()
if (targetCommand.isValidLocalhost()) {
return local()
} else if (targetCommand.isValidRemote()) {
} else if (targetCommand.isValidRemote() && remoteTarget != null) {
return createProvInstanceRemote(
targetCommand.remoteHost!!,
targetCommand.userName!!,
targetCommand.sshWithKey,
remoteTarget.host,
remoteTarget.user,
remoteTarget.password == null,
password,
remoteHostSetSudoWithoutPasswordRequired
)
@ -81,15 +81,13 @@ private fun createProvInstanceRemote(
}
// todo: consider removal as password can be retrieved by PromptSecretSource
internal fun retrievePassword(cliCommand: TargetCliCommand): Secret? {
var password: Secret? = null
if (cliCommand.isValidRemote()) {
if (cliCommand.sshWithPasswordPrompt) {
password =
PromptSecretSource("Password for user $cliCommand.userName!! on $cliCommand.remoteHost!!").secret()
} else if (cliCommand.sshWithGopassPath != null) {
password = GopassSecretSource(cliCommand.sshWithGopassPath).secret()
}
if (cliCommand.isValidRemote() && cliCommand.passwordInteractive) {
password =
PromptSecretSource("Password for user $cliCommand.userName!! on $cliCommand.remoteHost!!").secret()
}
return password
}

View file

@ -24,12 +24,8 @@ class CliArgumentsParser(name: String) : CliTargetParser(name) {
return ServerCliCommand(
ServerType.valueOf(module.name.uppercase()),
TargetCliCommand(
localHost,
remoteHost,
userName,
sshWithPasswordPrompt,
sshWithGopassPath,
sshWithKey
target,
passwordInteractive
),
module.configFileName
)
@ -41,14 +37,15 @@ class CliArgumentsParser(name: String) : CliTargetParser(name) {
}
class K3s : ServerSubcommand("k3s", "the k3s module") {
val cliConfigFileName by argument(
val cliConfigFileName by option(
ArgType.String,
"configFilename",
"config-file",
"c",
"the filename containing the yaml config for k3s"
)
override fun execute() {
super.configFileName = ConfigFileName(cliConfigFileName)
super.configFileName = cliConfigFileName?.let { ConfigFileName(it) }
super.parsed = true
}
}

View file

@ -1,64 +1,47 @@
package org.domaindrivenarchitecture.provs.configuration.application
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
internal class CliTargetParserTest {
@Test
fun parse_localhost_with_default() {
val parseCli = parseTarget(args = emptyArray())
assertFalse(parseCli.isValidLocalhost())
assertFalse(parseCli.isValidRemote())
assertFalse(parseCli.isValid())
}
@Test
fun parse_localhost() {
val parseCli = parseTarget(args = arrayOf("-l"))
assertTrue(parseCli.isValidLocalhost())
assertFalse(parseCli.isValidRemote())
assertTrue(parseCli.isValid())
val cliCommand = parseTarget(args = arrayOf("local"))
assertTrue(cliCommand.isValidLocalhost())
assertFalse(cliCommand.isValidRemote())
assertTrue(cliCommand.isValid())
}
@Test
fun parse_remote_with_missing_passwordoption() {
val parseCli = parseTarget(args = arrayOf("-r", "1.2.3.4", "-u", "user"))
fun parse_remote_with_given_pasword() {
val cliCommand = parseTarget(args = arrayOf("user:mypassword@1.2.3.4"))
assertFalse(parseCli.isValidLocalhost())
assertEquals("1.2.3.4", parseCli.remoteHost)
assertEquals("user", parseCli.userName)
assertFalse(parseCli.isValidRemote())
assertFalse(parseCli.isValid())
assertFalse(cliCommand.isValidLocalhost())
assertEquals("1.2.3.4", cliCommand.remoteTarget()?.host)
assertEquals("user", cliCommand.remoteTarget()?.user)
assertEquals("mypassword", cliCommand.remoteTarget()?.password?.plain())
assertTrue(cliCommand.isValid())
}
@Test
fun parse_remote_with_remote_key() {
val parseCli = parseTarget(args = arrayOf("-r", "1.2.3.4", "-u", "user", "-k"))
fun parse_remote_with_ssh_key() {
val cliCommand = parseTarget(args = arrayOf("user@1.2.3.4"))
assertFalse(parseCli.isValidLocalhost())
assertEquals("1.2.3.4", parseCli.remoteHost)
assertEquals("user", parseCli.userName)
assertTrue(parseCli.isValid())
assertFalse(cliCommand.isValidLocalhost())
assertEquals("1.2.3.4", cliCommand.remoteTarget()?.host)
assertEquals("user", cliCommand.remoteTarget()?.user)
assertTrue(cliCommand.isValid())
}
@Test
fun parse_remote_with_remote_password_prompt() {
val parseCli = parseTarget(args = arrayOf("-r", "1.2.3.4", "-u", "user", "-i"))
@Disabled // enable to enter manually the password when prompted
fun parse_remote_with_password_prompt() {
val cliCommand = parseTarget(args = arrayOf("user@1.2.3.4", "-p"))
assertEquals("1.2.3.4", parseCli.remoteHost)
assertEquals("user", parseCli.userName)
assertTrue(parseCli.isValid())
}
@Test
fun parse_remote_with_remote_password_gopass_path() {
val parseCli = parseTarget(args = arrayOf("-r", "1.2.3.4", "-u", "user", "-p", "gopass/path"))
assertEquals("1.2.3.4", parseCli.remoteHost)
assertEquals("user", parseCli.userName)
assertEquals("gopass/path", parseCli.sshWithGopassPath)
assertTrue(parseCli.isValid())
assertEquals("1.2.3.4", cliCommand.remoteTarget()?.host)
assertEquals("user", cliCommand.remoteTarget()?.user)
assertTrue(cliCommand.isValid())
}
}

View file

@ -41,7 +41,7 @@ internal class CliTargetCommandKtTest {
@Test
fun createProvInstance_local() {
// given
val cliCommand = TargetCliCommand(true, null, null, false, null, false)
val cliCommand = TargetCliCommand("local", false)
// when
createProvInstance(cliCommand)
@ -53,7 +53,7 @@ internal class CliTargetCommandKtTest {
@Test
fun createProvInstance_remote_with_sshKey() {
// given
val cliCommand = TargetCliCommand(false, "host123", "user123", false, null, true)
val cliCommand = TargetCliCommand("user123@host123", false)
// when
createProvInstance(cliCommand)
@ -65,7 +65,7 @@ internal class CliTargetCommandKtTest {
@Test
fun createProvInstance_remote_with_interactive_password_retrieval() {
// given
val cliCommand = TargetCliCommand(false, "host123", "user123", true, null, false)
val cliCommand = TargetCliCommand("user123:sec@host123", false)
// when
createProvInstance(cliCommand)

View file

@ -8,10 +8,10 @@ internal class CliArgumentsParserTest {
@Test
fun parse_cliCommand_with_module_and_local_target() {
val cli = CliArgumentsParser("test").parseCommand(args = arrayOf("basic", "-l"))
val cli = CliArgumentsParser("test").parseCommand(args = arrayOf("basic", "local"))
assertTrue(cli.isValid())
assertEquals(null, cli.configFile)
assertEquals(true, cli.target.localHost)
assertEquals(true, cli.target.isValidLocalhost())
}
}

View file

@ -27,7 +27,7 @@ internal class CliWorkplaceKtTest {
val testConfig = DesktopConfig(gitUserName = "gittestuser", gitEmail = "git@test.mail")
val cmd = DesktopCliCommand(
DesktopType.BASIC,
TargetCliCommand(null, null, null, false, null, false),
TargetCliCommand("user@host", false),
ConfigFileName("bla")
)
@ -74,7 +74,7 @@ internal class CliWorkplaceKtTest {
fun provision_workplace_remotely() {
// when
main(arrayOf("basic", "-i", "-r", "host123.xyz", "-u", "user123", "-c", "testconfig.yaml"))
main(arrayOf("basic", "user123:sec@host123.xyz", "-c", "testconfig.yaml"))
// then
verify { remote("host123.xyz", "user123", Secret("sec"), any()) }
@ -103,7 +103,7 @@ internal class CliWorkplaceKtTest {
System.setErr(PrintStream(errContent))
// when
main(arrayOf("basic", "-c", "idontexist.yaml", "-r", "remotehost", "-u", "someuser", "-k"))
main(arrayOf("basic", "someuser@remotehost", "-c", "idontexist.yaml"))
// then
System.setOut(originalOut)
@ -130,7 +130,7 @@ internal class CliWorkplaceKtTest {
System.setErr(PrintStream(errContent))
// when
main(arrayOf("basic", "-c", "src/test/resources/InvalidWorkplaceConfig.yaml", "-r", "remotehost", "-u", "someuser", "-k"))
main(arrayOf("basic", "someuser@remotehost", "-c", "src/test/resources/InvalidWorkplaceConfig.yaml"))
// then
System.setOut(originalOut)

View file

@ -1,24 +1,15 @@
package org.domaindrivenarchitecture.provs.framework.extensions.workplace
import org.domaindrivenarchitecture.provs.configuration.domain.ConfigFileName
import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
import org.domaindrivenarchitecture.provs.desktop.domain.DesktopCliCommand
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
import org.domaindrivenarchitecture.provs.desktop.domain.DesktopType
import org.domaindrivenarchitecture.provs.desktop.domain.provisionWorkplace
import org.domaindrivenarchitecture.provs.desktop.infrastructure.getConfig
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
import org.domaindrivenarchitecture.provs.test.tags.ContainerTest
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
internal class ProvisionWorkplaceKtTest {
val cmd = DesktopCliCommand(
DesktopType.BASIC,
TargetCliCommand(null, null, null, false, null, false),
ConfigFileName("bla")
)
@Test
@ContainerTest
fun provisionWorkplace() {

View file

@ -12,7 +12,7 @@ internal class CliArgumentParserTest {
val parser = CliArgumentsParser("test")
// when
val result = parser.parseCommand(args = arrayOf("k3s", "-l", "config.yaml"))
val result = parser.parseCommand(args = arrayOf("k3s", "local", "-c", "config.yaml"))
// then
assertTrue(result.isValid())