Introduce DDD & add terraform installation
This commit is contained in:
parent
25c60b28a2
commit
a5856fb422
13 changed files with 294 additions and 49 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@
|
|||
/.gradle/
|
||||
|
||||
/.idea/
|
||||
/MyIdeConfig.yaml
|
||||
|
|
11
.idea-configs/cli-MyIdeConfig.yaml.run.xml
Normal file
11
.idea-configs/cli-MyIdeConfig.yaml.run.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="cli-MyIdeConfig.yaml" type="Application" factoryName="Application">
|
||||
<option name="ALTERNATIVE_JRE_PATH" value="Kotlin SDK" />
|
||||
<option name="MAIN_CLASS_NAME" value="org.domaindrivenarchitecture.provs.application.CliKt" />
|
||||
<module name="provs.main" />
|
||||
<option name="PROGRAM_PARAMETERS" value="MyIdeConfig.yaml" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
12
build.gradle
12
build.gradle
|
@ -68,6 +68,10 @@ dependencies {
|
|||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.2.2"
|
||||
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-cli:0.3.2"
|
||||
implementation 'com.charleskorn.kaml:kaml:0.35.2'
|
||||
|
||||
implementation group: 'com.hierynomus', name: 'sshj', version: '0.31.0'
|
||||
|
||||
api "org.slf4j:slf4j-api:1.7.32"
|
||||
|
@ -84,7 +88,7 @@ task fatJar(type: Jar) {
|
|||
manifest {
|
||||
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
||||
'Implementation-Version': project.version,
|
||||
'Main-Class': 'org.domaindrivenarchitecture.provs.core.entry.EntryKt'
|
||||
'Main-Class': 'org.domaindrivenarchitecture.provs.application.CliKt'
|
||||
}
|
||||
archivesBaseName = project.name + '-all'
|
||||
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
|
||||
|
@ -104,7 +108,7 @@ task fatJarLatest(type: Jar) {
|
|||
manifest {
|
||||
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
||||
'Implementation-Version': project.version,
|
||||
'Main-Class': 'org.domaindrivenarchitecture.provs.core.entry.EntryKt'
|
||||
'Main-Class': 'org.domaindrivenarchitecture.provs.application.CliKt'
|
||||
}
|
||||
with jar
|
||||
archiveFileName = 'provs-fat-latest.jar'
|
||||
|
@ -126,7 +130,7 @@ task uberJar(type: Jar) {
|
|||
manifest {
|
||||
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
||||
'Implementation-Version': project.version,
|
||||
'Main-Class': 'org.domaindrivenarchitecture.provs.core.entry.EntryKt'
|
||||
'Main-Class': 'org.domaindrivenarchitecture.provs.application.CliKt'
|
||||
}
|
||||
archiveClassifier = 'uber'
|
||||
}
|
||||
|
@ -147,7 +151,7 @@ task uberJarLatest(type: Jar) {
|
|||
manifest {
|
||||
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
||||
'Implementation-Version': project.version,
|
||||
'Main-Class': 'org.domaindrivenarchitecture.provs.core.entry.EntryKt'
|
||||
'Main-Class': 'org.domaindrivenarchitecture.provs.application.CliKt'
|
||||
}
|
||||
archiveFileName = 'provs-latest.jar'
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package org.domaindrivenarchitecture.provs.application
|
||||
|
||||
import org.domaindrivenarchitecture.provs.core.Password
|
||||
import org.domaindrivenarchitecture.provs.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||
import org.domaindrivenarchitecture.provs.domain.WorkplaceConfig
|
||||
import org.domaindrivenarchitecture.provs.domain.WorkplaceType
|
||||
import org.domaindrivenarchitecture.provs.infrastructure.installDevOps
|
||||
|
||||
/**
|
||||
* Use case for provisioning repos
|
||||
*/
|
||||
fun Prov.provision(conf: WorkplaceConfig, let: Password?) = def {
|
||||
if (conf.type == WorkplaceType.IDE) {
|
||||
installDevOps()
|
||||
}
|
||||
ProvResult(true)
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package org.domaindrivenarchitecture.provs.application
|
||||
|
||||
import org.domaindrivenarchitecture.provs.core.*
|
||||
import org.domaindrivenarchitecture.provs.infrastructure.getConfig
|
||||
import org.domaindrivenarchitecture.provs.ubuntu.secret.secretSources.GopassSecretSource
|
||||
import org.domaindrivenarchitecture.provs.ubuntu.secret.secretSources.PromptSecretSource
|
||||
|
||||
import java.lang.RuntimeException
|
||||
|
||||
/**
|
||||
* Provisions according to the options either a meissa workplace, reposOnly or gopassOnly.
|
||||
* Locally or on a remote machine. If remotely, the remote host and remote user are specified by args parameters.
|
||||
*
|
||||
* Get help with:
|
||||
* java -jar build/libs/provs-meissa-latest.jar meissa.provs.application.CliKt main -h
|
||||
*/
|
||||
fun main(args: Array<String>) {
|
||||
val cliCommand = parseCli(args)
|
||||
if (cliCommand.isValid()) {
|
||||
provision(cliCommand)
|
||||
} else {
|
||||
println("Invalid command line options.\nPlease use option -h for help.")
|
||||
System.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun provision(cliCommand: CliCommand) {
|
||||
val filename = cliCommand.configFileName
|
||||
|
||||
// TODO: mv try-catch down to repository, throw runtime exc.
|
||||
try {
|
||||
val conf = getConfig(filename)
|
||||
val password: Secret? = retrievePassword(cliCommand)
|
||||
val prov: Prov = createProvInstance(cliCommand, password)
|
||||
|
||||
prov.provision(conf, password?.let { Password(password.plain()) })
|
||||
} catch (e: IllegalArgumentException) {
|
||||
println(
|
||||
"Error: File\u001b[31m $filename \u001b[0m was not found.\n" +
|
||||
"Pls copy file \u001B[31m MeissaWorkplaceConfigExample.yaml \u001B[0m to file \u001B[31m $filename \u001B[0m " +
|
||||
"and change the content according to your needs.\n"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createProvInstance(
|
||||
cliCommand: CliCommand,
|
||||
password: Secret?
|
||||
): Prov {
|
||||
if (cliCommand.isValid()) {
|
||||
if (cliCommand.isValidRemote()) {
|
||||
val host = cliCommand.remoteHost!!
|
||||
val remoteUser = cliCommand.userName!!
|
||||
if (cliCommand.sshWithKey) {
|
||||
return remote(host, remoteUser)
|
||||
} else {
|
||||
require(
|
||||
password != null,
|
||||
{ "No password available for provisioning without ssh keys. Either specify provisioning by ssh-keys or provide password." })
|
||||
return remote(host, remoteUser, password)
|
||||
}
|
||||
} else {
|
||||
return local()
|
||||
}
|
||||
} else {
|
||||
throw RuntimeException("Invalid cliCommand")
|
||||
}
|
||||
}
|
||||
|
||||
private fun retrievePassword(cliCommand: CliCommand): 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()
|
||||
}
|
||||
}
|
||||
return password
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
package org.domaindrivenarchitecture.provs.application
|
||||
|
||||
import kotlinx.cli.ArgParser
|
||||
import kotlinx.cli.ArgType
|
||||
import kotlinx.cli.default
|
||||
import kotlinx.cli.optional
|
||||
|
||||
class CliCommand(
|
||||
val remoteHost: String?,
|
||||
val userName: String?,
|
||||
val sshWithGopassPath: String?,
|
||||
val sshWithPasswordPrompt: Boolean,
|
||||
val sshWithKey: Boolean,
|
||||
_configFileName: String?
|
||||
) {
|
||||
val configFileName: String
|
||||
|
||||
init {
|
||||
configFileName = _configFileName ?: "WorkplaceConfig.yaml"
|
||||
}
|
||||
|
||||
fun isValidLocalhost(): Boolean {
|
||||
return remoteHost == null && userName == null && sshWithGopassPath == null &&
|
||||
!sshWithPasswordPrompt && !sshWithKey
|
||||
}
|
||||
|
||||
fun hasValidPasswordOption(): Boolean {
|
||||
return (sshWithGopassPath != null) xor sshWithPasswordPrompt xor sshWithKey
|
||||
}
|
||||
|
||||
fun isValidRemote(): Boolean {
|
||||
return remoteHost != null && userName != null && hasValidPasswordOption()
|
||||
}
|
||||
|
||||
fun isValid(): Boolean {
|
||||
return (isValidLocalhost() || isValidRemote())
|
||||
}
|
||||
}
|
||||
|
||||
fun parseCli(args: Array<String>): CliCommand {
|
||||
val parser = ArgParser("meissa.provs.application.CliKt main")
|
||||
|
||||
val configFileName by parser.argument(ArgType.String, description = "the config file name to apply").optional()
|
||||
|
||||
val remoteHost by parser.option(
|
||||
ArgType.String, shortName =
|
||||
"r", description = "provision remote host"
|
||||
)
|
||||
val userName by parser.option(
|
||||
ArgType.String,
|
||||
shortName = "u",
|
||||
description = "user for remote provisioning."
|
||||
)
|
||||
val sshWithGopassPath by parser.option(
|
||||
ArgType.String,
|
||||
shortName = "p",
|
||||
description = "password stored at gopass path"
|
||||
)
|
||||
val sshWithPasswordPrompt by parser.option(
|
||||
ArgType.Boolean,
|
||||
shortName = "i",
|
||||
description = "prompt for password interactive"
|
||||
).default(false)
|
||||
val sshWithKey by parser.option(
|
||||
ArgType.Boolean,
|
||||
shortName = "k",
|
||||
description = "provision over ssh using user & ssh key"
|
||||
).default(false)
|
||||
parser.parse(args)
|
||||
val cliCommand =
|
||||
CliCommand(
|
||||
remoteHost, userName, sshWithGopassPath, sshWithPasswordPrompt, sshWithKey, configFileName
|
||||
)
|
||||
return cliCommand
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package org.domaindrivenarchitecture.provs.domain
|
||||
|
||||
import com.charleskorn.kaml.Yaml
|
||||
import org.domaindrivenarchitecture.provs.ubuntu.keys.KeyPairSource
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.domaindrivenarchitecture.provs.core.tags.Api
|
||||
import java.io.*
|
||||
|
||||
|
||||
@Serializable
|
||||
class WorkplaceConfig(
|
||||
val type: WorkplaceType = WorkplaceType.MINIMAL,
|
||||
val ssh: KeyPairSource? = null,
|
||||
val gpg: KeyPairSource? = null,
|
||||
val gitUserName: String? = null,
|
||||
val gitEmail: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package org.domaindrivenarchitecture.provs.domain
|
||||
|
||||
enum class WorkplaceType {
|
||||
MINIMAL, OFFICE, IDE
|
||||
}
|
|
@ -2,7 +2,10 @@ package org.domaindrivenarchitecture.provs.extensions.workplace
|
|||
|
||||
import org.domaindrivenarchitecture.provs.core.*
|
||||
import org.domaindrivenarchitecture.provs.core.processors.RemoteProcessor
|
||||
import org.domaindrivenarchitecture.provs.domain.WorkplaceConfig
|
||||
import org.domaindrivenarchitecture.provs.domain.WorkplaceType
|
||||
import org.domaindrivenarchitecture.provs.extensions.workplace.base.*
|
||||
import org.domaindrivenarchitecture.provs.infrastructure.getConfig
|
||||
import org.domaindrivenarchitecture.provs.ubuntu.git.provisionGit
|
||||
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstallFromPpa
|
||||
|
@ -18,11 +21,6 @@ import java.net.InetAddress
|
|||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
enum class WorkplaceType {
|
||||
MINIMAL, OFFICE, IDE
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Provisions software and configurations for a personal workplace.
|
||||
* Offers the possibility to choose between different types.
|
||||
|
@ -128,7 +126,7 @@ fun provisionRemote(args: Array<String>) {
|
|||
val pwSecret = PromptSecretSource("Password for user $userName on $host").secret()
|
||||
val pwFromSecret = Password(pwSecret.plain())
|
||||
|
||||
val config = readWorkplaceConfigFromFile() ?: WorkplaceConfig()
|
||||
val config = getConfig() ?: WorkplaceConfig()
|
||||
Prov.newInstance(RemoteProcessor(host, userName, pwFromSecret)).provisionWorkplace(
|
||||
config.type,
|
||||
config.ssh?.keyPair(),
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
package org.domaindrivenarchitecture.provs.extensions.workplace
|
||||
|
||||
import org.domaindrivenarchitecture.provs.ubuntu.keys.KeyPairSource
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.*
|
||||
|
||||
|
||||
@Serializable
|
||||
class WorkplaceConfig(
|
||||
val type: WorkplaceType = WorkplaceType.MINIMAL,
|
||||
val ssh: KeyPairSource? = null,
|
||||
val gpg: KeyPairSource? = null,
|
||||
val gitUserName: String? = null,
|
||||
val gitEmail: String? = null,
|
||||
)
|
||||
|
||||
|
||||
// -------------------------------------------- file methods ------------------------------------
|
||||
fun readWorkplaceConfigFromFile(filename: String = "WorkplaceConfig.json"): WorkplaceConfig? {
|
||||
val file = File(filename)
|
||||
return if (file.exists())
|
||||
try {
|
||||
// read from file
|
||||
val inputAsString = BufferedReader(FileReader(filename)).use { it.readText() }
|
||||
|
||||
return Json.decodeFromString(WorkplaceConfig.serializer(), inputAsString)
|
||||
} catch (e: FileNotFoundException) {
|
||||
null
|
||||
} else null
|
||||
}
|
||||
|
||||
|
||||
fun writeWorkplaceConfigToFile(config: WorkplaceConfig) {
|
||||
val fileName = "WorkplaceConfig.json"
|
||||
|
||||
FileWriter(fileName).use { it.write(Json.encodeToString(WorkplaceConfig.serializer(), config)) }
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.domaindrivenarchitecture.provs.infrastructure
|
||||
|
||||
import com.charleskorn.kaml.Yaml
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.domaindrivenarchitecture.provs.core.tags.Api
|
||||
import org.domaindrivenarchitecture.provs.domain.WorkplaceConfig
|
||||
import java.io.*
|
||||
|
||||
|
||||
internal fun getConfig(filename: String = "WorkplaceConfig.json"): WorkplaceConfig {
|
||||
val file = File(filename)
|
||||
require(file.exists(), { "File not found: " + filename })
|
||||
|
||||
val config =
|
||||
try {
|
||||
// read from file
|
||||
val inputAsString = BufferedReader(FileReader(filename)).use { it.readText() }
|
||||
|
||||
// serializing objects
|
||||
if (filename.lowercase().endsWith(".yaml")) {
|
||||
Yaml.default.decodeFromString(WorkplaceConfig.serializer(), inputAsString)
|
||||
} else {
|
||||
Json.decodeFromString(WorkplaceConfig.serializer(), inputAsString)
|
||||
}
|
||||
} catch (e: FileNotFoundException) {
|
||||
throw IllegalArgumentException("File not found: " + filename, e)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
@Api
|
||||
internal fun writeConfig(config: WorkplaceConfig, fileName: String = "WorkplaceConfig.yaml") {
|
||||
if (fileName.lowercase().endsWith(".yaml")) {
|
||||
FileWriter(fileName).use {
|
||||
it.write(
|
||||
Yaml.default.encodeToString(
|
||||
WorkplaceConfig.serializer(),
|
||||
config
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
FileWriter(fileName).use { it.write(Json.encodeToString(WorkplaceConfig.serializer(), config)) }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package org.domaindrivenarchitecture.provs.infrastructure
|
||||
|
||||
import org.domaindrivenarchitecture.provs.core.Prov
|
||||
import org.domaindrivenarchitecture.provs.core.ProvResult
|
||||
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.createDirs
|
||||
import org.domaindrivenarchitecture.provs.ubuntu.filesystem.base.dirExists
|
||||
|
||||
fun Prov.installDevOps() = def {
|
||||
installTerraform()
|
||||
}
|
||||
|
||||
fun Prov.installTerraform(): ProvResult = def {
|
||||
val dir = "/usr/lib/tfenv/"
|
||||
|
||||
if(!dirExists(dir)) {
|
||||
createDirs(dir, sudo = true)
|
||||
cmd("git clone https://github.com/tfutils/tfenv.git " + dir, sudo = true)
|
||||
cmd("rm " + dir + ".git/ -rf", sudo = true)
|
||||
cmd("ln -s " + dir + "bin/* /usr/local/bin", sudo = true)
|
||||
}
|
||||
cmd ("tfenv install", sudo = true)
|
||||
cmd ("tfenv install latest:^0.13", sudo = true)
|
||||
cmd ("tfenv use latest:^0.13", sudo = true)
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package org.domaindrivenarchitecture.provs.extensions.workplace
|
||||
|
||||
import org.domaindrivenarchitecture.provs.core.Password
|
||||
import org.domaindrivenarchitecture.provs.domain.WorkplaceType
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||
|
|
Loading…
Reference in a new issue