[skip ci] refactor ensureSudoWithoutPassword to application layer
This commit is contained in:
parent
8a4bbe97f9
commit
1497d390f6
5 changed files with 159 additions and 77 deletions
|
@ -0,0 +1,32 @@
|
|||
package org.domaindrivenarchitecture.provs.configuration.application
|
||||
|
||||
import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
|
||||
import org.domaindrivenarchitecture.provs.framework.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.framework.core.cli.createRemoteProvInstance
|
||||
import org.domaindrivenarchitecture.provs.framework.core.cli.getPasswordToConfigureSudoWithoutPassword
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.currentUserCanSudoWithoutPassword
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.makeCurrentUserSudoerWithoutPasswordRequired
|
||||
|
||||
|
||||
fun ensureSudoWithoutPassword(prov: Prov, targetCommand: TargetCliCommand): Prov {
|
||||
|
||||
return if (prov.currentUserCanSudoWithoutPassword()) {
|
||||
prov
|
||||
} else {
|
||||
val password = targetCommand.remoteTarget()?.password ?: getPasswordToConfigureSudoWithoutPassword()
|
||||
|
||||
val result = prov.makeCurrentUserSudoerWithoutPasswordRequired(password)
|
||||
|
||||
check(result.success) {
|
||||
"Could not make user a sudoer without password required. (E.g. the password provided may be incorrect.)"
|
||||
}
|
||||
|
||||
return if (targetCommand.isValidRemote()) {
|
||||
// return a new instance as for remote instances a new ssh client is required after user was made sudoer without password
|
||||
createRemoteProvInstance(targetCommand.remoteTarget())
|
||||
} else {
|
||||
prov
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package org.domaindrivenarchitecture.provs.desktop.application
|
||||
|
||||
import kotlinx.serialization.SerializationException
|
||||
import org.domaindrivenarchitecture.provs.configuration.application.ensureSudoWithoutPassword
|
||||
import org.domaindrivenarchitecture.provs.desktop.domain.provisionDesktopCommand
|
||||
import org.domaindrivenarchitecture.provs.framework.core.cli.createProvInstance
|
||||
import java.io.FileNotFoundException
|
||||
|
@ -18,9 +19,10 @@ fun main(args: Array<String>) {
|
|||
}
|
||||
|
||||
val prov = createProvInstance(cmd.target)
|
||||
val provWithSudo = ensureSudoWithoutPassword(prov, cmd.target)
|
||||
|
||||
try {
|
||||
provisionDesktopCommand(prov, cmd)
|
||||
provisionDesktopCommand(provWithSudo, cmd)
|
||||
} catch (e: SerializationException) {
|
||||
println(
|
||||
"Error: File \"${cmd.configFile?.fileName}\" has an invalid format and or invalid data.\n"
|
||||
|
|
|
@ -6,9 +6,6 @@ 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.PromptSecretSource
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.currentUserCanSudoWithoutPassword
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.makeCurrentUserSudoerWithoutPasswordRequired
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.whoami
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
|
@ -21,17 +18,10 @@ fun createProvInstance(targetCommand: TargetCliCommand): Prov {
|
|||
if (targetCommand.isValid()) {
|
||||
val password: Secret? = targetCommand.remoteTarget()?.password
|
||||
|
||||
val remoteTarget = targetCommand.remoteTarget()
|
||||
|
||||
return if (targetCommand.isValidLocalhost()) {
|
||||
createLocalProvInstance()
|
||||
} else if (targetCommand.isValidRemote() && remoteTarget != null) {
|
||||
createRemoteProvInstance(
|
||||
remoteTarget.host,
|
||||
remoteTarget.user,
|
||||
remoteTarget.password == null,
|
||||
password
|
||||
)
|
||||
local()
|
||||
} else if (targetCommand.isValidRemote()) {
|
||||
createRemoteProvInstance(targetCommand.remoteTarget(), password)
|
||||
} else {
|
||||
throw IllegalArgumentException(
|
||||
"Error: neither a valid localHost nor a valid remoteHost was specified! Use option -h for help."
|
||||
|
@ -44,62 +34,21 @@ fun createProvInstance(targetCommand: TargetCliCommand): Prov {
|
|||
}
|
||||
}
|
||||
|
||||
private fun createLocalProvInstance(): Prov {
|
||||
val prov = local()
|
||||
if (!prov.currentUserCanSudoWithoutPassword()) {
|
||||
val passwordNonNull = getPasswordToConfigureSudoWithoutPassword()
|
||||
|
||||
prov.makeCurrentUserSudoerWithoutPasswordRequired(passwordNonNull)
|
||||
|
||||
check(prov.currentUserCanSudoWithoutPassword()) {
|
||||
"ERROR: User ${prov.whoami()} cannot sudo without enteringa password."
|
||||
}
|
||||
}
|
||||
return prov
|
||||
}
|
||||
|
||||
|
||||
private fun createRemoteProvInstance(
|
||||
host: String,
|
||||
remoteUser: String,
|
||||
sshWithKey: Boolean,
|
||||
password: Secret?
|
||||
internal fun createRemoteProvInstance(
|
||||
target: TargetCliCommand.RemoteTarget?,
|
||||
password: Secret? = null
|
||||
): Prov {
|
||||
val prov =
|
||||
if (sshWithKey) {
|
||||
remote(host, remoteUser)
|
||||
return if (target != null) {
|
||||
remote(target.host, target.user, target.password ?: password)
|
||||
} else {
|
||||
require(password != null) {
|
||||
"No password available for provisioning without ssh keys. " +
|
||||
"Either specify provisioning by ssh-keys or provide password."
|
||||
}
|
||||
remote(host, remoteUser, password)
|
||||
}
|
||||
|
||||
return if (prov.currentUserCanSudoWithoutPassword()) {
|
||||
prov
|
||||
} else {
|
||||
|
||||
val passwordNonNull = password
|
||||
?: getPasswordToConfigureSudoWithoutPassword()
|
||||
|
||||
val result = prov.makeCurrentUserSudoerWithoutPasswordRequired(passwordNonNull)
|
||||
|
||||
check(result.success) {
|
||||
"Could not make user a sudoer without password required. (Maybe the provided password is incorrect.)"
|
||||
}
|
||||
|
||||
// a new session is required after the user has become a sudoer without password
|
||||
val provWithNewSshClient = remote(host, remoteUser, password)
|
||||
|
||||
check(provWithNewSshClient.currentUserCanSudoWithoutPassword()) {
|
||||
"ERROR: User ${provWithNewSshClient.whoami()} on $host cannot sudo without entering a password."
|
||||
}
|
||||
|
||||
provWithNewSshClient
|
||||
throw IllegalArgumentException(
|
||||
"Error: no valid remote target (host & user) was specified!"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun getPasswordToConfigureSudoWithoutPassword(): Secret {
|
||||
return PromptSecretSource("password to configure sudo without password.").secret()
|
||||
}
|
|
@ -5,7 +5,6 @@ import org.domaindrivenarchitecture.provs.framework.core.Prov
|
|||
import org.domaindrivenarchitecture.provs.framework.core.docker.provideContainer
|
||||
import org.domaindrivenarchitecture.provs.framework.core.escapeAndEncloseByDoubleQuoteForShell
|
||||
import org.domaindrivenarchitecture.provs.framework.core.platforms.SHELL
|
||||
import org.domaindrivenarchitecture.provs.framework.core.tags.Api
|
||||
|
||||
enum class ContainerStartMode {
|
||||
USE_RUNNING_ELSE_CREATE,
|
||||
|
@ -20,26 +19,24 @@ enum class ContainerEndMode {
|
|||
|
||||
open class ContainerUbuntuHostProcessor(
|
||||
private val containerName: String = "default_provs_container",
|
||||
@Api // suppress false positive warning
|
||||
private val dockerImage: String = "ubuntu",
|
||||
@Api // suppress false positive warning
|
||||
private val startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
||||
dockerImage: String = "ubuntu",
|
||||
startMode: ContainerStartMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
||||
private val endMode: ContainerEndMode = ContainerEndMode.KEEP_RUNNING,
|
||||
@Api // suppress false positive warning
|
||||
private val sudo: Boolean = true
|
||||
sudo: Boolean = true,
|
||||
options: String = ""
|
||||
) : Processor {
|
||||
|
||||
private val hostShell = "/bin/bash"
|
||||
private val dockerCmd = if (sudo) "sudo docker " else "docker "
|
||||
private var localExecution = LocalProcessor()
|
||||
private var a = Prov.newInstance(name = "LocalProcessor for Docker operations", progressType = ProgressType.NONE)
|
||||
|
||||
init {
|
||||
val r = a.provideContainer(containerName, dockerImage, startMode, sudo)
|
||||
if (!r.success)
|
||||
throw RuntimeException("Could not start docker image: " + r.toString(), r.exception)
|
||||
val result = a.provideContainer(containerName, dockerImage, startMode, sudo, options)
|
||||
if (!result.success)
|
||||
throw RuntimeException("Could not start docker image: " + result.toString(), result.exception)
|
||||
}
|
||||
|
||||
private val hostShell = "/bin/bash"
|
||||
|
||||
override fun exec(vararg args: String): ProcessResult {
|
||||
return localExecution.exec(hostShell, "-c", dockerCmd + "exec $containerName " + buildCommand(*args))
|
||||
}
|
||||
|
@ -57,7 +54,7 @@ open class ContainerUbuntuHostProcessor(
|
|||
return s.escapeAndEncloseByDoubleQuoteForShell()
|
||||
}
|
||||
|
||||
private fun buildCommand(vararg args: String) : String {
|
||||
private fun buildCommand(vararg args: String): String {
|
||||
return if (args.size == 1) quoteString(args[0]) else
|
||||
if (args.size == 3 && SHELL == args[0] && "-c" == args[1]) SHELL + " -c " + quoteString(args[2])
|
||||
else args.joinToString(separator = " ")
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
package org.domaindrivenarchitecture.provs.configuration.application
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import org.domaindrivenarchitecture.provs.configuration.domain.TargetCliCommand
|
||||
import org.domaindrivenarchitecture.provs.framework.core.*
|
||||
import org.domaindrivenarchitecture.provs.framework.core.cli.getPasswordToConfigureSudoWithoutPassword
|
||||
import org.domaindrivenarchitecture.provs.framework.core.docker.provideContainer
|
||||
import org.domaindrivenarchitecture.provs.framework.core.processors.ContainerStartMode
|
||||
import org.domaindrivenarchitecture.provs.framework.core.processors.ContainerUbuntuHostProcessor
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.filesystem.base.deleteFile
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.install.base.aptInstall
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.currentUserCanSudoWithoutPassword
|
||||
import org.domaindrivenarchitecture.provs.framework.ubuntu.user.base.makeCurrentUserSudoerWithoutPasswordRequired
|
||||
import org.domaindrivenarchitecture.provs.test.tags.ExtensiveContainerTest
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
|
||||
|
||||
class ProvWithSudoKtTest {
|
||||
|
||||
|
||||
@ExtensiveContainerTest
|
||||
fun test_ensureSudoWithoutPassword_local_Prov() {
|
||||
|
||||
mockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||
every { getPasswordToConfigureSudoWithoutPassword() } returns Secret("testuserpw")
|
||||
|
||||
// given
|
||||
val containerName = "prov-test-sudo-no-pw"
|
||||
local().provideContainer(containerName, "ubuntu_plus_user")
|
||||
val prov = Prov.newInstance(
|
||||
ContainerUbuntuHostProcessor(
|
||||
containerName,
|
||||
startMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
||||
sudo = true,
|
||||
dockerImage = "ubuntu_plus_user"
|
||||
),
|
||||
progressType = ProgressType.NONE
|
||||
)
|
||||
prov.deleteFile("/etc/sudoers.d/testuser", sudo = true) // remove no password required config
|
||||
|
||||
// when
|
||||
val canSudo1 = prov.currentUserCanSudoWithoutPassword()
|
||||
val provWithSudo = ensureSudoWithoutPassword(
|
||||
prov, TargetCliCommand("local")
|
||||
)
|
||||
val canSudo2 = provWithSudo.currentUserCanSudoWithoutPassword()
|
||||
|
||||
// then
|
||||
assertFalse(canSudo1)
|
||||
assertTrue(canSudo2)
|
||||
|
||||
unmockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||
}
|
||||
|
||||
@ExtensiveContainerTest
|
||||
fun test_ensureSudoWithoutPassword_remote_Prov() {
|
||||
|
||||
// mockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||
// every { getPasswordToConfigureSudoWithoutPassword() } returns Secret("testuserpw")
|
||||
|
||||
// given
|
||||
val containerName = "prov-test-sudo-no-pw-ssh"
|
||||
val password = Secret("testuserpw")
|
||||
|
||||
// local().provideContainer(containerName, "ubuntu_plus_user", options = "")
|
||||
val prov = Prov.newInstance(
|
||||
ContainerUbuntuHostProcessor(
|
||||
containerName,
|
||||
startMode = ContainerStartMode.USE_RUNNING_ELSE_CREATE,
|
||||
sudo = true,
|
||||
dockerImage = "ubuntu_plus_user",
|
||||
options = "--expose=22"
|
||||
),
|
||||
progressType = ProgressType.NONE
|
||||
)
|
||||
prov.makeCurrentUserSudoerWithoutPasswordRequired(password)
|
||||
prov.task {
|
||||
aptInstall("openssh-server")
|
||||
cmd("sudo service ssh start")
|
||||
deleteFile("/etc/sudoers.d/testuser", sudo = true) // remove no password required config
|
||||
}
|
||||
val ip = local().cmd("sudo docker inspect -f \"{{ .NetworkSettings.IPAddress }}\" $containerName").out?.trim()
|
||||
?: throw IllegalStateException("Ip not found")
|
||||
val remoteProvBySsh = remote(ip, "testuser", password)
|
||||
|
||||
// when
|
||||
val canSudo1 = remoteProvBySsh.currentUserCanSudoWithoutPassword()
|
||||
val provWithSudo = ensureSudoWithoutPassword(
|
||||
remoteProvBySsh, TargetCliCommand("testuser:${password.plain()}@$ip")
|
||||
)
|
||||
val canSudo2 = provWithSudo.currentUserCanSudoWithoutPassword()
|
||||
|
||||
// then
|
||||
assertFalse(canSudo1)
|
||||
assertTrue(canSudo2)
|
||||
|
||||
// unmockkStatic(::getPasswordToConfigureSudoWithoutPassword)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue