diff --git a/build.gradle b/build.gradle
index 44608e7cc3..2776b05010 100644
--- a/build.gradle
+++ b/build.gradle
@@ -433,7 +433,7 @@ artifactory {
contextUrl = artifactory_contextUrl
repository {
repoKey = 'r3-corda-dev'
- username = 'teamcity'
+ username = System.getenv('CORDA_ARTIFACTORY_USERNAME')
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
}
diff --git a/docs/source/shell.rst b/docs/source/shell.rst
index 90fb188f3a..c7a34eb6ef 100644
--- a/docs/source/shell.rst
+++ b/docs/source/shell.rst
@@ -127,7 +127,7 @@ Where:
* ``config-file`` is the path to config file, used instead of providing the rest of command line options
* ``cordpass-directory`` is the directory containing Cordapps jars, Cordapps are require when starting flows
-* ``commands-directory`` is the directory with additional CrAsH shell commands
+* ``commands-directory`` is the directory with additional CRaSH shell commands
* ``host`` is the Corda node's host
* ``port`` is the Corda node's port, specified in the ``node.conf`` file
* ``user`` is the RPC username, if not provided it will be requested at startup
diff --git a/node/capsule/build.gradle b/node/capsule/build.gradle
index 20b074bebc..dcf25002f9 100644
--- a/node/capsule/build.gradle
+++ b/node/capsule/build.gradle
@@ -19,7 +19,7 @@ dependencies {
}
ext {
- quasarExcludeExpression = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**)"
+ quasarExcludeExpression = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**;org.jolokia**)"
applicationClass = 'net.corda.node.Corda'
}
diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt
index 4343c981eb..a7fc0170f8 100644
--- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt
+++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt
@@ -471,7 +471,7 @@ open class NodeStartup: CordaCliWrapper("corda", "Runs a Corda Node") {
}
override fun initLogging() {
- val loggingLevel = loggingLevel.name().toLowerCase(Locale.ENGLISH)
+ val loggingLevel = loggingLevel.name.toLowerCase(Locale.ENGLISH)
System.setProperty("defaultLogLevel", loggingLevel) // These properties are referenced from the XML config file.
if (verbose) {
System.setProperty("consoleLogLevel", loggingLevel)
diff --git a/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt b/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt
index 50252c38be..e1dbc171fd 100644
--- a/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt
+++ b/node/src/main/kotlin/net/corda/node/services/statemachine/SingleThreadedStateMachineManager.kt
@@ -134,6 +134,7 @@ class SingleThreadedStateMachineManager(
(fiber as FlowStateMachineImpl<*>).logger.warn("Caught exception from flow", throwable)
}
serviceHub.networkMapCache.nodeReady.then {
+ logger.info("Node ready, info: ${serviceHub.myInfo}")
resumeRestoredFlows(fibers)
flowMessaging.start { _, deduplicationHandler ->
executor.execute {
diff --git a/node/src/test/kotlin/net/corda/node/NodeCmdLineOptionsTest.kt b/node/src/test/kotlin/net/corda/node/NodeCmdLineOptionsTest.kt
index 2741d9c2c3..1ee0e163ea 100644
--- a/node/src/test/kotlin/net/corda/node/NodeCmdLineOptionsTest.kt
+++ b/node/src/test/kotlin/net/corda/node/NodeCmdLineOptionsTest.kt
@@ -3,10 +3,10 @@ package net.corda.node
import net.corda.core.internal.div
import net.corda.node.internal.NodeStartup
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
-import org.apache.logging.log4j.Level
import org.assertj.core.api.Assertions.assertThat
import org.junit.BeforeClass
import org.junit.Test
+import org.slf4j.event.Level
import java.nio.file.Path
import java.nio.file.Paths
diff --git a/tools/bootstrapper/build.gradle b/tools/bootstrapper/build.gradle
index de04e80796..48e2cb50be 100644
--- a/tools/bootstrapper/build.gradle
+++ b/tools/bootstrapper/build.gradle
@@ -1,4 +1,3 @@
-apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'com.jfrog.artifactory'
@@ -8,14 +7,15 @@ description 'Network bootstrapper'
dependencies {
compile project(':node-api')
compile project(':tools:cliutils')
- compile "info.picocli:picocli:$picocli_version"
- compile "org.slf4j:jul-to-slf4j:$slf4j_version"
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
- compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
+}
+
+processResources {
+ from file("$rootDir/config/dev/log4j2.xml")
}
jar {
- from(configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }) {
+ from(configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }) {
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
@@ -23,10 +23,9 @@ jar {
from(project(':node:capsule').tasks['buildCordaJAR']) {
rename 'corda-(.*)', 'corda.jar'
}
- archiveName = "network-bootstrapper-${corda_release_version}.jar"
+ baseName = "network-bootstrapper"
manifest {
attributes(
- 'Automatic-Module-Name': 'net.corda.bootstrapper',
'Main-Class': 'net.corda.bootstrapper.MainKt'
)
}
diff --git a/tools/bootstrapper/src/main/resources/log4j2.xml b/tools/bootstrapper/src/main/resources/log4j2.xml
deleted file mode 100644
index 98b3648e6b..0000000000
--- a/tools/bootstrapper/src/main/resources/log4j2.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- off
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/tools/cliutils/build.gradle b/tools/cliutils/build.gradle
index a73bd68cb7..8bd2e15687 100644
--- a/tools/cliutils/build.gradle
+++ b/tools/cliutils/build.gradle
@@ -1,6 +1,7 @@
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.publish-utils'
+apply plugin: 'com.jfrog.artifactory'
description 'CLI Utilities'
@@ -9,7 +10,7 @@ dependencies {
compile "info.picocli:picocli:$picocli_version"
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"
- compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
+ compile "org.slf4j:slf4j-api:$slf4j_version"
// JAnsi: for drawing things to the terminal in nicely coloured ways.
compile "org.fusesource.jansi:jansi:$jansi_version"
diff --git a/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt b/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt
index 1b9adcf04f..8d37b5f694 100644
--- a/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt
+++ b/tools/cliutils/src/main/kotlin/net/corda/cliutils/CordaCliWrapper.kt
@@ -4,10 +4,11 @@ import net.corda.core.internal.rootMessage
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.loggerFor
-import org.apache.logging.log4j.Level
import org.fusesource.jansi.AnsiConsole
+import org.slf4j.event.Level
import picocli.CommandLine
import picocli.CommandLine.*
+import java.nio.file.Paths
import kotlin.system.exitProcess
import java.util.*
import java.util.concurrent.Callable
@@ -127,11 +128,12 @@ abstract class CordaCliWrapper(val alias: String, val description: String) : Cal
// This needs to be called before loggers (See: NodeStartup.kt:51 logger called by lazy, initLogging happens before).
// Node's logging is more rich. In corda configurations two properties, defaultLoggingLevel and consoleLogLevel, are usually used.
open fun initLogging() {
- val loggingLevel = loggingLevel.name().toLowerCase(Locale.ENGLISH)
+ val loggingLevel = loggingLevel.name.toLowerCase(Locale.ENGLISH)
System.setProperty("defaultLogLevel", loggingLevel) // These properties are referenced from the XML config file.
if (verbose) {
System.setProperty("consoleLogLevel", loggingLevel)
}
+ System.setProperty("log-path", Paths.get(".").toString())
}
// Override this function with the actual method to be run once all the arguments have been parsed. The return number
@@ -147,11 +149,11 @@ abstract class CordaCliWrapper(val alias: String, val description: String) : Cal
}
/**
- * Converter from String to log4j logging Level.
+ * Converter from String to slf4j logging Level.
*/
class LoggingLevelConverter : ITypeConverter {
override fun convert(value: String?): Level {
- return value?.let { Level.getLevel(it) }
+ return value?.let { Level.valueOf(it.toUpperCase()) }
?: throw TypeConversionException("Unknown option for --logging-level: $value")
}
diff --git a/tools/shell-cli/README.md b/tools/shell-cli/README.md
index c6a00181cd..131fa0ba66 100644
--- a/tools/shell-cli/README.md
+++ b/tools/shell-cli/README.md
@@ -1,7 +1,7 @@
Standalone Shell
----------------
-Documentation for shell CLI can be found [here](http://docs.corda.net/website/releases/docs_head/shell.html)
+Documentation for the standalone shell can be found [here](https://docs.corda.net/head/shell.html#the-standalone-shell)
To build this from the command line on Unix or MacOS:
diff --git a/tools/shell-cli/build.gradle b/tools/shell-cli/build.gradle
index 89a74f394c..6cafc920fa 100644
--- a/tools/shell-cli/build.gradle
+++ b/tools/shell-cli/build.gradle
@@ -10,7 +10,9 @@ apply plugin: 'com.jfrog.artifactory'
dependencies {
compile project(':tools:shell')
- compile "org.slf4j:slf4j-simple:$slf4j_version"
+ compile project(':tools:cliutils')
+ compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
+ compile "org.slf4j:jul-to-slf4j:$slf4j_version"
testCompile(project(':test-utils')) {
exclude group: 'org.apache.logging.log4j', module: 'log4j-slf4j-impl'
diff --git a/tools/shell-cli/src/main/kotlin/net/corda/tools/shell/ShellCmdLineOptions.kt b/tools/shell-cli/src/main/kotlin/net/corda/tools/shell/ShellCmdLineOptions.kt
new file mode 100644
index 0000000000..35da8b0cf0
--- /dev/null
+++ b/tools/shell-cli/src/main/kotlin/net/corda/tools/shell/ShellCmdLineOptions.kt
@@ -0,0 +1,195 @@
+package net.corda.tools.shell
+
+import com.typesafe.config.Config
+import com.typesafe.config.ConfigFactory
+import net.corda.core.internal.div
+import net.corda.core.messaging.ClientRpcSslOptions
+import net.corda.core.utilities.NetworkHostAndPort
+import net.corda.nodeapi.internal.config.parseAs
+import net.corda.tools.shell.ShellConfiguration.Companion.COMMANDS_DIR
+import picocli.CommandLine.Option
+import java.nio.file.Path
+import java.nio.file.Paths
+
+class ShellCmdLineOptions {
+ @Option(
+ names = ["-f", "--config-file"],
+ description = ["The path to the shell configuration file, used instead of providing the rest of command line options."]
+ )
+ var configFile: Path? = null
+
+ @Option(
+ names = ["-c", "--cordapp-directory"],
+ description = ["The path to the directory containing CorDapp JARs, CorDapps are required when starting flows."]
+ )
+ var cordappDirectory: Path? = null
+
+ @Option(
+ names = ["-o", "--commands-directory"],
+ description = ["The path to the directory containing additional CRaSH shell commands."]
+ )
+ var commandsDirectory: Path? = null
+
+ @Option(
+ names = ["-a", "--host"],
+ description = ["The host address of the Corda node."]
+ )
+ var host: String? = null
+
+ @Option(
+ names = ["-p", "--port"],
+ description = ["The RPC port of the Corda node."]
+ )
+ var port: String? = null
+
+ @Option(
+ names = ["--user"],
+ description = ["The RPC user name."]
+ )
+ var user: String? = null
+
+ @Option(
+ names = ["--password"],
+ description = ["The RPC user password."]
+ )
+ var password: String? = null
+
+
+ @Option(
+ names = ["--sshd-port"],
+ description = ["Enables SSH server for shell."]
+ )
+ var sshdPort: String? = null
+
+ @Option(
+ names = ["--sshd-hostkey-directory"],
+ description = ["The directory with hostkey.pem file for SSH server."]
+ )
+ var sshdHostKeyDirectory: Path? = null
+
+ @Option(
+ names = ["--truststore-password"],
+ description = ["The password to unlock the TrustStore file."]
+ )
+ var trustStorePassword: String? = null
+
+
+ @Option(
+ names = ["--truststore-file"],
+ description = ["The path to the TrustStore file."]
+ )
+ var trustStoreFile: Path? = null
+
+
+ @Option(
+ names = ["--truststore-type"],
+ description = ["The type of the TrustStore (e.g. JKS)."]
+ )
+ var trustStoreType: String? = null
+
+
+ private fun toConfigFile(): Config {
+ val cmdOpts = mutableMapOf()
+
+ commandsDirectory?.apply { cmdOpts["extensions.commands.path"] = this.toString() }
+ cordappDirectory?.apply { cmdOpts["extensions.cordapps.path"] = this.toString() }
+ user?.apply { cmdOpts["node.user"] = this }
+ password?.apply { cmdOpts["node.password"] = this }
+ host?.apply { cmdOpts["node.addresses.rpc.host"] = this }
+ port?.apply { cmdOpts["node.addresses.rpc.port"] = this }
+ trustStoreFile?.apply { cmdOpts["ssl.truststore.path"] = this.toString() }
+ trustStorePassword?.apply { cmdOpts["ssl.truststore.password"] = this }
+ trustStoreType?.apply { cmdOpts["ssl.truststore.type"] = this }
+ sshdPort?.apply {
+ cmdOpts["extensions.sshd.port"] = this
+ cmdOpts["extensions.sshd.enabled"] = true
+ }
+ sshdHostKeyDirectory?.apply { cmdOpts["extensions.sshd.hostkeypath"] = this.toString() }
+
+ return ConfigFactory.parseMap(cmdOpts)
+ }
+
+ /** Return configuration parsed from an optional config file (provided by the command line option)
+ * and then overridden by the command line options */
+ fun toConfig(): ShellConfiguration {
+ val fileConfig = configFile?.let { ConfigFactory.parseFile(it.toFile()) }
+ ?: ConfigFactory.empty()
+ val typeSafeConfig = toConfigFile().withFallback(fileConfig).resolve()
+ val shellConfigFile = typeSafeConfig.parseAs()
+ return shellConfigFile.toShellConfiguration()
+ }
+}
+
+/** Object representation of Shell configuration file */
+private class ShellConfigurationFile {
+ data class Rpc(
+ val host: String,
+ val port: Int)
+
+ data class Addresses(
+ val rpc: Rpc
+ )
+
+ data class Node(
+ val addresses: Addresses,
+ val user: String?,
+ val password: String?
+ )
+
+ data class Cordapps(
+ val path: String
+ )
+
+ data class Sshd(
+ val enabled: Boolean,
+ val port: Int,
+ val hostkeypath: String?
+ )
+
+ data class Commands(
+ val path: String
+ )
+
+ data class Extensions(
+ val cordapps: Cordapps?,
+ val sshd: Sshd?,
+ val commands: Commands?
+ )
+
+ data class KeyStore(
+ val path: String,
+ val type: String = "JKS",
+ val password: String
+ )
+
+ data class Ssl(
+ val truststore: KeyStore
+ )
+
+ data class ShellConfigFile(
+ val node: Node,
+ val extensions: Extensions?,
+ val ssl: Ssl?
+ ) {
+ fun toShellConfiguration(): ShellConfiguration {
+
+ val sslOptions =
+ ssl?.let {
+ ClientRpcSslOptions(
+ trustStorePath = Paths.get(it.truststore.path),
+ trustStorePassword = it.truststore.password)
+ }
+
+ return ShellConfiguration(
+ commandsDirectory = extensions?.commands?.let { Paths.get(it.path) } ?: Paths.get(".")
+ / COMMANDS_DIR,
+ cordappsDirectory = extensions?.cordapps?.let { Paths.get(it.path) },
+ user = node.user ?: "",
+ password = node.password ?: "",
+ hostAndPort = NetworkHostAndPort(node.addresses.rpc.host, node.addresses.rpc.port),
+ ssl = sslOptions,
+ sshdPort = extensions?.sshd?.let { if (it.enabled) it.port else null },
+ sshHostKeyDirectory = extensions?.sshd?.let { if (it.enabled && it.hostkeypath != null) Paths.get(it.hostkeypath) else null })
+ }
+ }
+}
diff --git a/tools/shell-cli/src/main/kotlin/net/corda/tools/shell/StandaloneShell.kt b/tools/shell-cli/src/main/kotlin/net/corda/tools/shell/StandaloneShell.kt
index 00f8928b81..8d40c858ea 100644
--- a/tools/shell-cli/src/main/kotlin/net/corda/tools/shell/StandaloneShell.kt
+++ b/tools/shell-cli/src/main/kotlin/net/corda/tools/shell/StandaloneShell.kt
@@ -1,45 +1,34 @@
package net.corda.tools.shell
import com.jcabi.manifests.Manifests
-import joptsimple.OptionException
-import net.corda.core.internal.*
+import net.corda.cliutils.CordaCliWrapper
+import net.corda.cliutils.ExitCodes
+import net.corda.cliutils.start
+import net.corda.core.internal.exists
+import net.corda.core.internal.isRegularFile
+import net.corda.core.internal.list
import org.fusesource.jansi.Ansi
import org.fusesource.jansi.AnsiConsole
+import org.slf4j.bridge.SLF4JBridgeHandler
+import picocli.CommandLine.Mixin
+import java.io.BufferedReader
+import java.io.IOException
+import java.io.InputStreamReader
import java.net.URL
import java.net.URLClassLoader
import java.nio.file.Path
import java.util.concurrent.CountDownLatch
import kotlin.streams.toList
-import java.io.IOException
-import java.io.BufferedReader
-import java.io.InputStreamReader
-import kotlin.system.exitProcess
fun main(args: Array) {
-
- val argsParser = CommandLineOptionParser()
- val cmdlineOptions = try {
- argsParser.parse(*args)
- } catch (e: OptionException) {
- println("Invalid command line arguments: ${e.message}")
- argsParser.printHelp(System.out)
- exitProcess(1)
- }
-
- if (cmdlineOptions.help) {
- argsParser.printHelp(System.out)
- return
- }
- val config = try {
- cmdlineOptions.toConfig()
- } catch(e: Exception) {
- println("Configuration exception: ${e.message}")
- exitProcess(1)
- }
- StandaloneShell(config).run()
+ StandaloneShell().start(args)
}
-class StandaloneShell(private val configuration: ShellConfiguration) {
+class StandaloneShell : CordaCliWrapper("corda-shell", "The Corda standalone shell.") {
+ @Mixin
+ var cmdLineOptions = ShellCmdLineOptions()
+
+ lateinit var configuration: ShellConfiguration
private fun getCordappsInDirectory(cordappsDir: Path?): List =
if (cordappsDir == null || !cordappsDir.exists()) {
@@ -67,7 +56,20 @@ class StandaloneShell(private val configuration: ShellConfiguration) {
private fun getManifestEntry(key: String) = if (Manifests.exists(key)) Manifests.read(key) else "Unknown"
- fun run() {
+ override fun initLogging() {
+ super.initLogging()
+ SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler.
+ SLF4JBridgeHandler.install()
+ }
+
+ override fun runProgram(): Int {
+ configuration = try {
+ cmdLineOptions.toConfig()
+ } catch(e: Exception) {
+ println("Configuration exception: ${e.message}")
+ return ExitCodes.FAILURE
+ }
+
val cordappJarPaths = getCordappsInDirectory(configuration.cordappsDirectory)
val classLoader: ClassLoader = URLClassLoader(cordappJarPaths.toTypedArray(), javaClass.classLoader)
with(configuration) {
@@ -84,7 +86,7 @@ class StandaloneShell(private val configuration: ShellConfiguration) {
InteractiveShell.nodeInfo()
} catch (e: Exception) {
println("Cannot login to ${configuration.hostAndPort}, reason: \"${e.message}\"")
- exitProcess(1)
+ return ExitCodes.FAILURE
}
val exit = CountDownLatch(1)
@@ -106,6 +108,6 @@ class StandaloneShell(private val configuration: ShellConfiguration) {
exit.await()
// because we can't clean certain Crash Shell threads that block on read()
- exitProcess(0)
+ return ExitCodes.SUCCESS
}
}
diff --git a/tools/shell-cli/src/main/kotlin/net/corda/tools/shell/StandaloneShellArgsParser.kt b/tools/shell-cli/src/main/kotlin/net/corda/tools/shell/StandaloneShellArgsParser.kt
deleted file mode 100644
index 9b628948e9..0000000000
--- a/tools/shell-cli/src/main/kotlin/net/corda/tools/shell/StandaloneShellArgsParser.kt
+++ /dev/null
@@ -1,207 +0,0 @@
-package net.corda.tools.shell
-
-import com.typesafe.config.Config
-import com.typesafe.config.ConfigFactory
-import joptsimple.OptionParser
-import joptsimple.util.EnumConverter
-import net.corda.core.internal.div
-import net.corda.core.utilities.NetworkHostAndPort
-import net.corda.core.messaging.ClientRpcSslOptions
-import net.corda.nodeapi.internal.config.parseAs
-import net.corda.tools.shell.ShellConfiguration.Companion.COMMANDS_DIR
-import org.slf4j.event.Level
-import java.io.PrintStream
-import java.nio.file.Path
-import java.nio.file.Paths
-
-// NOTE: Do not use any logger in this class as args parsing is done before the logger is setup.
-class CommandLineOptionParser {
- private val optionParser = OptionParser()
-
- private val configFileArg = optionParser
- .accepts("config-file", "The path to the shell configuration file, used instead of providing the rest of command line options.")
- .withOptionalArg()
- private val cordappsDirectoryArg = optionParser
- .accepts("cordpass-directory", "The path to directory containing Cordapps jars, Cordapps are require when starting flows.")
- .withOptionalArg()
- private val commandsDirectoryArg = optionParser
- .accepts("commands-directory", "The directory with additional CrAsH shell commands.")
- .withOptionalArg()
- private val hostArg = optionParser
- .acceptsAll(listOf("h", "host"), "The host of the Corda node.")
- .withRequiredArg()
- private val portArg = optionParser
- .acceptsAll(listOf("p", "port"), "The port of the Corda node.")
- .withRequiredArg()
- private val userArg = optionParser
- .accepts("user", "The RPC user name.")
- .withOptionalArg()
- private val passwordArg = optionParser
- .accepts("password", "The RPC user password.")
- .withOptionalArg()
- private val loggerLevel = optionParser
- .accepts("logging-level", "Enable logging at this level and higher.")
- .withRequiredArg()
- .withValuesConvertedBy(object : EnumConverter(Level::class.java) {})
- .defaultsTo(Level.INFO)
- private val sshdPortArg = optionParser
- .accepts("sshd-port", "Enables SSH server for shell.")
- .withOptionalArg()
- private val sshdHostKeyDirectoryArg = optionParser
- .accepts("sshd-hostkey-directory", "The directory with hostkey.pem file for SSH server.")
- .withOptionalArg()
- private val helpArg = optionParser
- .accepts("help")
- .forHelp()
- private val trustStorePasswordArg = optionParser
- .accepts("truststore-password", "The password to unlock the TrustStore file.")
- .withOptionalArg()
- private val trustStoreDirArg = optionParser
- .accepts("truststore-file", "The path to the TrustStore file.")
- .withOptionalArg()
- private val trustStoreTypeArg = optionParser
- .accepts("truststore-type", "The type of the TrustStore (e.g. JKS).")
- .withOptionalArg()
-
- fun parse(vararg args: String): CommandLineOptions {
- val optionSet = optionParser.parse(*args)
- return CommandLineOptions(
- configFile = optionSet.valueOf(configFileArg),
- host = optionSet.valueOf(hostArg),
- port = optionSet.valueOf(portArg),
- user = optionSet.valueOf(userArg),
- password = optionSet.valueOf(passwordArg),
- commandsDirectory = (optionSet.valueOf(commandsDirectoryArg))?.let { Paths.get(it).normalize().toAbsolutePath() },
- cordappsDirectory = (optionSet.valueOf(cordappsDirectoryArg))?.let { Paths.get(it).normalize().toAbsolutePath() },
- help = optionSet.has(helpArg),
- loggingLevel = optionSet.valueOf(loggerLevel),
- sshdPort = optionSet.valueOf(sshdPortArg),
- sshdHostKeyDirectory = (optionSet.valueOf(sshdHostKeyDirectoryArg))?.let { Paths.get(it).normalize().toAbsolutePath() },
- trustStorePassword = optionSet.valueOf(trustStorePasswordArg),
- trustStoreFile = (optionSet.valueOf(trustStoreDirArg))?.let { Paths.get(it).normalize().toAbsolutePath() },
- trustStoreType = optionSet.valueOf(trustStoreTypeArg))
- }
-
- fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink)
-}
-
-data class CommandLineOptions(val configFile: String?,
- val commandsDirectory: Path?,
- val cordappsDirectory: Path?,
- val host: String?,
- val port: String?,
- val user: String?,
- val password: String?,
- val help: Boolean,
- val loggingLevel: Level,
- val sshdPort: String?,
- val sshdHostKeyDirectory: Path?,
- val trustStorePassword: String?,
- val trustStoreFile: Path?,
- val trustStoreType: String?) {
-
- private fun toConfigFile(): Config {
- val cmdOpts = mutableMapOf()
-
- commandsDirectory?.apply { cmdOpts["extensions.commands.path"] = this.toString() }
- cordappsDirectory?.apply { cmdOpts["extensions.cordapps.path"] = this.toString() }
- user?.apply { cmdOpts["node.user"] = this }
- password?.apply { cmdOpts["node.password"] = this }
- host?.apply { cmdOpts["node.addresses.rpc.host"] = this }
- port?.apply { cmdOpts["node.addresses.rpc.port"] = this }
- trustStoreFile?.apply { cmdOpts["ssl.truststore.path"] = this.toString() }
- trustStorePassword?.apply { cmdOpts["ssl.truststore.password"] = this }
- trustStoreType?.apply { cmdOpts["ssl.truststore.type"] = this }
- sshdPort?.apply {
- cmdOpts["extensions.sshd.port"] = this
- cmdOpts["extensions.sshd.enabled"] = true
- }
- sshdHostKeyDirectory?.apply { cmdOpts["extensions.sshd.hostkeypath"] = this.toString() }
-
- return ConfigFactory.parseMap(cmdOpts)
- }
-
- /** Return configuration parsed from an optional config file (provided by the command line option)
- * and then overridden by the command line options */
- fun toConfig(): ShellConfiguration {
- val fileConfig = configFile?.let { ConfigFactory.parseFile(Paths.get(configFile).toFile()) }
- ?: ConfigFactory.empty()
- val typeSafeConfig = toConfigFile().withFallback(fileConfig).resolve()
- val shellConfigFile = typeSafeConfig.parseAs()
- return shellConfigFile.toShellConfiguration()
- }
-}
-
-/** Object representation of Shell configuration file */
-private class ShellConfigurationFile {
- data class Rpc(
- val host: String,
- val port: Int)
-
- data class Addresses(
- val rpc: Rpc
- )
-
- data class Node(
- val addresses: Addresses,
- val user: String?,
- val password: String?
- )
-
- data class Cordapps(
- val path: String
- )
-
- data class Sshd(
- val enabled: Boolean,
- val port: Int,
- val hostkeypath: String?
- )
-
- data class Commands(
- val path: String
- )
-
- data class Extensions(
- val cordapps: Cordapps,
- val sshd: Sshd,
- val commands: Commands?
- )
-
- data class KeyStore(
- val path: String,
- val type: String = "JKS",
- val password: String
- )
-
- data class Ssl(
- val truststore: KeyStore
- )
-
- data class ShellConfigFile(
- val node: Node,
- val extensions: Extensions?,
- val ssl: Ssl?
- ) {
- fun toShellConfiguration(): ShellConfiguration {
-
- val sslOptions =
- ssl?.let {
- ClientRpcSslOptions(
- trustStorePath = Paths.get(it.truststore.path),
- trustStorePassword = it.truststore.password)
- }
-
- return ShellConfiguration(
- commandsDirectory = extensions?.commands?.let { Paths.get(it.path) } ?: Paths.get(".")
- / COMMANDS_DIR,
- cordappsDirectory = extensions?.cordapps?.let { Paths.get(it.path) },
- user = node.user ?: "",
- password = node.password ?: "",
- hostAndPort = NetworkHostAndPort(node.addresses.rpc.host, node.addresses.rpc.port),
- ssl = sslOptions,
- sshdPort = extensions?.sshd?.let { if (it.enabled) it.port else null },
- sshHostKeyDirectory = extensions?.sshd?.let { if (it.enabled && it.hostkeypath != null) Paths.get(it.hostkeypath) else null })
- }
- }
-}
diff --git a/tools/shell-cli/src/test/kotlin/net/corda/tools/shell/StandaloneShellArgsParserTest.kt b/tools/shell-cli/src/test/kotlin/net/corda/tools/shell/StandaloneShellArgsParserTest.kt
index d1817a822a..8563e65f26 100644
--- a/tools/shell-cli/src/test/kotlin/net/corda/tools/shell/StandaloneShellArgsParserTest.kt
+++ b/tools/shell-cli/src/test/kotlin/net/corda/tools/shell/StandaloneShellArgsParserTest.kt
@@ -1,97 +1,44 @@
package net.corda.tools.shell
import net.corda.core.internal.toPath
-import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.messaging.ClientRpcSslOptions
-import org.assertj.core.api.Assertions.assertThat
+import net.corda.core.utilities.NetworkHostAndPort
import org.junit.Test
-import org.slf4j.event.Level
import java.nio.file.Paths
import kotlin.test.assertEquals
class StandaloneShellArgsParserTest {
private val CONFIG_FILE = StandaloneShellArgsParserTest::class.java.getResource("/config.conf").toPath()
- @Test
- fun args_to_cmd_options() {
- val args = arrayOf("--config-file", "/x/y/z/config.conf",
- "--commands-directory", "/x/y/commands",
- "--cordpass-directory", "/x/y/cordapps",
- "--host", "alocalhost",
- "--port", "1234",
- "--user", "demo",
- "--password", "abcd1234",
- "--logging-level", "DEBUG",
- "--sshd-port", "2223",
- "--sshd-hostkey-directory", "/x/y/ssh",
- "--help",
- "--truststore-password", "pass2",
- "--truststore-file", "/x/y/truststore.jks",
- "--truststore-type", "dummy")
-
- val expectedOptions = CommandLineOptions(
- configFile = "/x/y/z/config.conf",
- commandsDirectory = Paths.get("/x/y/commands").normalize().toAbsolutePath(),
- cordappsDirectory = Paths.get("/x/y/cordapps").normalize().toAbsolutePath(),
- host = "alocalhost",
- port = "1234",
- user = "demo",
- password = "abcd1234",
- help = true,
- loggingLevel = Level.DEBUG,
- sshdPort = "2223",
- sshdHostKeyDirectory = Paths.get("/x/y/ssh").normalize().toAbsolutePath(),
- trustStorePassword = "pass2",
- trustStoreFile = Paths.get("/x/y/truststore.jks").normalize().toAbsolutePath(),
- trustStoreType = "dummy")
-
- val options = CommandLineOptionParser().parse(*args)
-
- assertThat(options).isEqualTo(expectedOptions)
- }
-
@Test
fun empty_args_to_cmd_options() {
- val args = emptyArray()
+ val expectedOptions = ShellCmdLineOptions()
- val expectedOptions = CommandLineOptions(configFile = null,
- commandsDirectory = null,
- cordappsDirectory = null,
- host = null,
- port = null,
- user = null,
- password = null,
- help = false,
- loggingLevel = Level.INFO,
- sshdPort = null,
- sshdHostKeyDirectory = null,
- trustStorePassword = null,
- trustStoreFile = null,
- trustStoreType = null)
-
- val options = CommandLineOptionParser().parse(*args)
-
- assertEquals(expectedOptions, options)
+ assertEquals(expectedOptions.configFile, null)
+ assertEquals(expectedOptions.cordappDirectory, null)
+ assertEquals(expectedOptions.commandsDirectory, null)
+ assertEquals(expectedOptions.host, null)
+ assertEquals(expectedOptions.port, null)
+ assertEquals(expectedOptions.user, null)
+ assertEquals(expectedOptions.password, null)
+ assertEquals(expectedOptions.sshdPort, null)
}
@Test
fun args_to_config() {
-
- val options = CommandLineOptions(configFile = null,
- commandsDirectory = Paths.get("/x/y/commands"),
- cordappsDirectory = Paths.get("/x/y/cordapps"),
- host = "alocalhost",
- port = "1234",
- user = "demo",
- password = "abcd1234",
- help = true,
- loggingLevel = Level.DEBUG,
- sshdPort = "2223",
- sshdHostKeyDirectory = Paths.get("/x/y/ssh"),
- trustStorePassword = "pass2",
- trustStoreFile = Paths.get("/x/y/truststore.jks"),
- trustStoreType = "dummy"
- )
+ val options = ShellCmdLineOptions()
+ options.configFile = null
+ options.commandsDirectory = Paths.get("/x/y/commands")
+ options.cordappDirectory = Paths.get("/x/y/cordapps")
+ options.host = "alocalhost"
+ options.port = "1234"
+ options.user = "demo"
+ options.password = "abcd1234"
+ options.sshdPort = "2223"
+ options.sshdHostKeyDirectory = Paths.get("/x/y/ssh")
+ options.trustStorePassword = "pass2"
+ options.trustStoreFile = Paths.get("/x/y/truststore.jks")
+ options.trustStoreType = "dummy"
val expectedSsl = ClientRpcSslOptions(
trustStorePath = Paths.get("/x/y/truststore.jks"),
@@ -114,21 +61,19 @@ class StandaloneShellArgsParserTest {
@Test
fun cmd_options_to_config_from_file() {
-
- val options = CommandLineOptions(configFile = CONFIG_FILE.toString(),
- commandsDirectory = null,
- cordappsDirectory = null,
- host = null,
- port = null,
- user = null,
- password = null,
- help = false,
- loggingLevel = Level.DEBUG,
- sshdPort = null,
- sshdHostKeyDirectory = null,
- trustStorePassword = null,
- trustStoreFile = null,
- trustStoreType = null)
+ val options = ShellCmdLineOptions()
+ options.configFile = CONFIG_FILE
+ options.commandsDirectory = null
+ options.cordappDirectory = null
+ options.host = null
+ options.port = null
+ options.user = null
+ options.password = null
+ options.sshdPort = null
+ options.sshdHostKeyDirectory = null
+ options.trustStorePassword = null
+ options.trustStoreFile = null
+ options.trustStoreType = null
val expectedConfig = ShellConfiguration(
commandsDirectory = Paths.get("/x/y/commands"),
@@ -148,21 +93,18 @@ class StandaloneShellArgsParserTest {
@Test
fun cmd_options_override_config_from_file() {
-
- val options = CommandLineOptions(configFile = CONFIG_FILE.toString(),
- commandsDirectory = null,
- cordappsDirectory = null,
- host = null,
- port = null,
- user = null,
- password = "blabla",
- help = false,
- loggingLevel = Level.DEBUG,
- sshdPort = null,
- sshdHostKeyDirectory = null,
- trustStorePassword = null,
- trustStoreFile = null,
- trustStoreType = null)
+ val options = ShellCmdLineOptions()
+ options.configFile = CONFIG_FILE
+ options.commandsDirectory = null
+ options.host = null
+ options.port = null
+ options.user = null
+ options.password = "blabla"
+ options.sshdPort = null
+ options.sshdHostKeyDirectory = null
+ options.trustStorePassword = null
+ options.trustStoreFile = null
+ options.trustStoreType = null
val expectedSsl = ClientRpcSslOptions(
trustStorePath = Paths.get("/x/y/truststore.jks"),