diff --git a/.gitignore b/.gitignore
index 7041416..de9beb0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
/.gradle/
/.idea/
+/MyIdeConfig.yaml
diff --git a/.idea-configs/cli-MyIdeConfig.yaml.run.xml b/.idea-configs/cli-MyIdeConfig.yaml.run.xml
new file mode 100644
index 0000000..d09b3c6
--- /dev/null
+++ b/.idea-configs/cli-MyIdeConfig.yaml.run.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index ddab90a..f403fd5 100644
--- a/build.gradle
+++ b/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'
}
@@ -183,4 +187,4 @@ publishing {
mavenLocal()
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/application/Application.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/application/Application.kt
new file mode 100644
index 0000000..4646c3c
--- /dev/null
+++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/application/Application.kt
@@ -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)
+}
diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/application/Cli.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/application/Cli.kt
new file mode 100644
index 0000000..8ca937f
--- /dev/null
+++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/application/Cli.kt
@@ -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) {
+ 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
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/application/CliCommand.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/application/CliCommand.kt
new file mode 100644
index 0000000..4f1bf37
--- /dev/null
+++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/application/CliCommand.kt
@@ -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): 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
+}
diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/domain/WorkplaceConfig.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/domain/WorkplaceConfig.kt
new file mode 100644
index 0000000..58a6746
--- /dev/null
+++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/domain/WorkplaceConfig.kt
@@ -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,
+)
+
diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/domain/WorkplaceType.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/domain/WorkplaceType.kt
new file mode 100644
index 0000000..c241147
--- /dev/null
+++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/domain/WorkplaceType.kt
@@ -0,0 +1,5 @@
+package org.domaindrivenarchitecture.provs.domain
+
+enum class WorkplaceType {
+ MINIMAL, OFFICE, IDE
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/workplace/ProvisionWorkplace.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/workplace/ProvisionWorkplace.kt
index f51ee21..4159c72 100644
--- a/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/workplace/ProvisionWorkplace.kt
+++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/workplace/ProvisionWorkplace.kt
@@ -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) {
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(),
diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/workplace/WorkplaceConfig.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/workplace/WorkplaceConfig.kt
deleted file mode 100644
index de75195..0000000
--- a/src/main/kotlin/org/domaindrivenarchitecture/provs/extensions/workplace/WorkplaceConfig.kt
+++ /dev/null
@@ -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)) }
-}
diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/infrastructure/ConfigRepository.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/infrastructure/ConfigRepository.kt
new file mode 100644
index 0000000..a23cb16
--- /dev/null
+++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/infrastructure/ConfigRepository.kt
@@ -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)) }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/org/domaindrivenarchitecture/provs/infrastructure/DevOps.kt b/src/main/kotlin/org/domaindrivenarchitecture/provs/infrastructure/DevOps.kt
new file mode 100644
index 0000000..b176d47
--- /dev/null
+++ b/src/main/kotlin/org/domaindrivenarchitecture/provs/infrastructure/DevOps.kt
@@ -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)
+}
+
diff --git a/src/test/kotlin/org/domaindrivenarchitecture/provs/extensions/workplace/ProvisionWorkplaceKtTest.kt b/src/test/kotlin/org/domaindrivenarchitecture/provs/extensions/workplace/ProvisionWorkplaceKtTest.kt
index 752a210..74834db 100644
--- a/src/test/kotlin/org/domaindrivenarchitecture/provs/extensions/workplace/ProvisionWorkplaceKtTest.kt
+++ b/src/test/kotlin/org/domaindrivenarchitecture/provs/extensions/workplace/ProvisionWorkplaceKtTest.kt
@@ -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