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/
|
/.gradle/
|
||||||
|
|
||||||
/.idea/
|
/.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-json:1.2.2"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-core: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'
|
implementation group: 'com.hierynomus', name: 'sshj', version: '0.31.0'
|
||||||
|
|
||||||
api "org.slf4j:slf4j-api:1.7.32"
|
api "org.slf4j:slf4j-api:1.7.32"
|
||||||
|
@ -84,7 +88,7 @@ task fatJar(type: Jar) {
|
||||||
manifest {
|
manifest {
|
||||||
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
||||||
'Implementation-Version': project.version,
|
'Implementation-Version': project.version,
|
||||||
'Main-Class': 'org.domaindrivenarchitecture.provs.core.entry.EntryKt'
|
'Main-Class': 'org.domaindrivenarchitecture.provs.application.CliKt'
|
||||||
}
|
}
|
||||||
archivesBaseName = project.name + '-all'
|
archivesBaseName = project.name + '-all'
|
||||||
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
|
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
|
||||||
|
@ -104,7 +108,7 @@ task fatJarLatest(type: Jar) {
|
||||||
manifest {
|
manifest {
|
||||||
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
||||||
'Implementation-Version': project.version,
|
'Implementation-Version': project.version,
|
||||||
'Main-Class': 'org.domaindrivenarchitecture.provs.core.entry.EntryKt'
|
'Main-Class': 'org.domaindrivenarchitecture.provs.application.CliKt'
|
||||||
}
|
}
|
||||||
with jar
|
with jar
|
||||||
archiveFileName = 'provs-fat-latest.jar'
|
archiveFileName = 'provs-fat-latest.jar'
|
||||||
|
@ -126,7 +130,7 @@ task uberJar(type: Jar) {
|
||||||
manifest {
|
manifest {
|
||||||
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
||||||
'Implementation-Version': project.version,
|
'Implementation-Version': project.version,
|
||||||
'Main-Class': 'org.domaindrivenarchitecture.provs.core.entry.EntryKt'
|
'Main-Class': 'org.domaindrivenarchitecture.provs.application.CliKt'
|
||||||
}
|
}
|
||||||
archiveClassifier = 'uber'
|
archiveClassifier = 'uber'
|
||||||
}
|
}
|
||||||
|
@ -147,7 +151,7 @@ task uberJarLatest(type: Jar) {
|
||||||
manifest {
|
manifest {
|
||||||
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
attributes 'Implementation-Title': 'Gradle Jar File Example',
|
||||||
'Implementation-Version': project.version,
|
'Implementation-Version': project.version,
|
||||||
'Main-Class': 'org.domaindrivenarchitecture.provs.core.entry.EntryKt'
|
'Main-Class': 'org.domaindrivenarchitecture.provs.application.CliKt'
|
||||||
}
|
}
|
||||||
archiveFileName = 'provs-latest.jar'
|
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.*
|
||||||
import org.domaindrivenarchitecture.provs.core.processors.RemoteProcessor
|
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.extensions.workplace.base.*
|
||||||
|
import org.domaindrivenarchitecture.provs.infrastructure.getConfig
|
||||||
import org.domaindrivenarchitecture.provs.ubuntu.git.provisionGit
|
import org.domaindrivenarchitecture.provs.ubuntu.git.provisionGit
|
||||||
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstall
|
||||||
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstallFromPpa
|
import org.domaindrivenarchitecture.provs.ubuntu.install.base.aptInstallFromPpa
|
||||||
|
@ -18,11 +21,6 @@ import java.net.InetAddress
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
|
||||||
enum class WorkplaceType {
|
|
||||||
MINIMAL, OFFICE, IDE
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provisions software and configurations for a personal workplace.
|
* Provisions software and configurations for a personal workplace.
|
||||||
* Offers the possibility to choose between different types.
|
* 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 pwSecret = PromptSecretSource("Password for user $userName on $host").secret()
|
||||||
val pwFromSecret = Password(pwSecret.plain())
|
val pwFromSecret = Password(pwSecret.plain())
|
||||||
|
|
||||||
val config = readWorkplaceConfigFromFile() ?: WorkplaceConfig()
|
val config = getConfig() ?: WorkplaceConfig()
|
||||||
Prov.newInstance(RemoteProcessor(host, userName, pwFromSecret)).provisionWorkplace(
|
Prov.newInstance(RemoteProcessor(host, userName, pwFromSecret)).provisionWorkplace(
|
||||||
config.type,
|
config.type,
|
||||||
config.ssh?.keyPair(),
|
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
|
package org.domaindrivenarchitecture.provs.extensions.workplace
|
||||||
|
|
||||||
import org.domaindrivenarchitecture.provs.core.Password
|
import org.domaindrivenarchitecture.provs.core.Password
|
||||||
|
import org.domaindrivenarchitecture.provs.domain.WorkplaceType
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
import org.domaindrivenarchitecture.provs.test.defaultTestContainer
|
||||||
|
|
Loading…
Reference in a new issue