diff --git a/bridge/build.gradle b/bridge/build.gradle index 78f9623138..7d4056ec1f 100644 --- a/bridge/build.gradle +++ b/bridge/build.gradle @@ -42,6 +42,10 @@ dependencies { transitive = false// we control dependencies directly as the bridge is likely to be audited } + compile(project(':tools:cliutils')) { + transitive = false// we control dependencies directly as the bridge is likely to be audited + } + // Here we pull in dependencies that would normally be pulled in transitively from :core and :node-api, but we need more fine grained control // For AMQP serialisation. compile "org.apache.qpid:proton-j:${protonj_version}" @@ -69,12 +73,15 @@ dependencies { compile "org.slf4j:jul-to-slf4j:$slf4j_version" compile "org.slf4j:jcl-over-slf4j:$slf4j_version" - // JOpt: for command line flags. - compile "net.sf.jopt-simple:jopt-simple:$jopt_simple_version" - // Manifests: for reading stuff from the manifest file compile "com.jcabi:jcabi-manifests:1.1" + // Picocli: for parsing command line options + compile "info.picocli:picocli:$picocli_version" + + // JAnsi: for drawing things to the terminal in nicely coloured ways. + compile "org.fusesource.jansi:jansi:$jansi_version" + integrationTestCompile project(':node-driver') integrationTestCompile "org.apache.curator:curator-test:${curator_version}" testCompile "junit:junit:$junit_version" diff --git a/bridge/src/main/kotlin/net/corda/bridge/Firewall.kt b/bridge/src/main/kotlin/net/corda/bridge/Firewall.kt index 411158fc11..15bf04c157 100644 --- a/bridge/src/main/kotlin/net/corda/bridge/Firewall.kt +++ b/bridge/src/main/kotlin/net/corda/bridge/Firewall.kt @@ -3,8 +3,9 @@ package net.corda.bridge import net.corda.bridge.internal.FirewallStartup +import net.corda.cliutils.start import kotlin.system.exitProcess fun main(args: Array) { - exitProcess(if (FirewallStartup(args).run()) 0 else 1) + FirewallStartup().start(args) } diff --git a/bridge/src/main/kotlin/net/corda/bridge/FirewallArgsParser.kt b/bridge/src/main/kotlin/net/corda/bridge/FirewallArgsParser.kt deleted file mode 100644 index 9dc82917e4..0000000000 --- a/bridge/src/main/kotlin/net/corda/bridge/FirewallArgsParser.kt +++ /dev/null @@ -1,66 +0,0 @@ -package net.corda.bridge - -import joptsimple.OptionParser -import joptsimple.util.EnumConverter -import net.corda.bridge.services.api.FirewallConfiguration -import net.corda.bridge.services.config.BridgeConfigHelper -import net.corda.bridge.services.config.parseAsFirewallConfiguration -import net.corda.core.internal.div -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 ArgsParser { - private val optionParser = OptionParser() - // The intent of allowing a command line configurable directory and config path is to allow deployment flexibility. - // Other general configuration should live inside the config file unless we regularly need temporary overrides on the command line - private val baseDirectoryArg = optionParser - .accepts("base-directory", "The firewall working directory where all the files are kept") - .withRequiredArg() - .defaultsTo(".") - private val configFileArg = optionParser - .accepts("config-file", "The path to the config file") - .withRequiredArg() - .defaultsTo("firewall.conf") - 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 logToConsoleArg = optionParser.accepts("log-to-console", "If set, prints logging to the console as well as to a file.") - private val isVersionArg = optionParser.accepts("version", "Print the version and exit") - private val helpArg = optionParser.accepts("help").forHelp() - - fun parse(vararg args: String): CmdLineOptions { - val optionSet = optionParser.parse(*args) - val baseDirectory = Paths.get(optionSet.valueOf(baseDirectoryArg)).normalize().toAbsolutePath() - val configFilePath = Paths.get(optionSet.valueOf(configFileArg)) - val configFile = if (configFilePath.isAbsolute) configFilePath else baseDirectory / configFilePath.toString() - val help = optionSet.has(helpArg) - val loggingLevel = optionSet.valueOf(loggerLevel) - val logToConsole = optionSet.has(logToConsoleArg) - val isVersion = optionSet.has(isVersionArg) - return CmdLineOptions(baseDirectory, - configFile, - help, - loggingLevel, - logToConsole, - isVersion) - } - - fun printHelp(sink: PrintStream) = optionParser.printHelpOn(sink) -} - -data class CmdLineOptions(val baseDirectory: Path, - val configFile: Path, - val help: Boolean, - val loggingLevel: Level, - val logToConsole: Boolean, - val isVersion: Boolean) { - fun loadConfig(): FirewallConfiguration { - val config = BridgeConfigHelper.loadConfig(baseDirectory, configFile).parseAsFirewallConfiguration() - return config - } -} diff --git a/bridge/src/main/kotlin/net/corda/bridge/FirewallCmdLineOptions.kt b/bridge/src/main/kotlin/net/corda/bridge/FirewallCmdLineOptions.kt new file mode 100644 index 0000000000..93b3a6c9fb --- /dev/null +++ b/bridge/src/main/kotlin/net/corda/bridge/FirewallCmdLineOptions.kt @@ -0,0 +1,28 @@ +package net.corda.bridge + +import net.corda.bridge.services.api.FirewallConfiguration +import net.corda.bridge.services.config.BridgeConfigHelper +import net.corda.bridge.services.config.parseAsFirewallConfiguration +import net.corda.core.internal.div +import picocli.CommandLine.Option +import java.nio.file.Path +import java.nio.file.Paths + +class FirewallCmdLineOptions { + @Option( + names = ["-b", "--base-directory"], + description = ["The firewall working directory where all the files are kept."] + ) + var baseDirectory: Path = Paths.get(".").toAbsolutePath().normalize() + + @Option( + names = ["-f", "--config-file"], + description = ["The path to the config file. By default this is firewall.conf in the base directory."] + ) + private var _configFile: Path? = null + val configFile: Path get() = _configFile ?: (baseDirectory / "firewall.conf") + + fun loadConfig(): FirewallConfiguration { + return BridgeConfigHelper.loadConfig(baseDirectory, configFile).parseAsFirewallConfiguration() + } +} \ No newline at end of file diff --git a/bridge/src/main/kotlin/net/corda/bridge/internal/FirewallStartup.kt b/bridge/src/main/kotlin/net/corda/bridge/internal/FirewallStartup.kt index 8cecb67ee5..cafa8d0bc7 100644 --- a/bridge/src/main/kotlin/net/corda/bridge/internal/FirewallStartup.kt +++ b/bridge/src/main/kotlin/net/corda/bridge/internal/FirewallStartup.kt @@ -1,74 +1,64 @@ package net.corda.bridge.internal -import com.jcabi.manifests.Manifests -import joptsimple.OptionException -import net.corda.bridge.ArgsParser -import net.corda.bridge.CmdLineOptions +import net.corda.bridge.FirewallCmdLineOptions import net.corda.bridge.FirewallVersionInfo import net.corda.bridge.services.api.FirewallConfiguration +import net.corda.cliutils.CordaCliWrapper +import net.corda.cliutils.CordaVersionProvider +import net.corda.cliutils.ExitCodes import net.corda.core.internal.createDirectories import net.corda.core.internal.div import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.addShutdownHook import org.slf4j.bridge.SLF4JBridgeHandler +import picocli.CommandLine.Mixin import sun.misc.VMSupport import java.io.RandomAccessFile import java.lang.management.ManagementFactory import java.net.InetAddress import java.nio.file.Path import java.util.* -import kotlin.system.exitProcess -class FirewallStartup(val args: Array) { +class FirewallStartup: CordaCliWrapper("corda-firewall", "The Corda Firewall application for handling outbound and inbound connections to Corda.") { companion object { // lazy init the logging, because the logging levels aren't configured until we have parsed some options. private val log by lazy { contextLogger() } val LOGS_DIRECTORY_NAME = "logs" } + @Mixin + val cmdLineOptions = FirewallCmdLineOptions() + /** - * @return true if the firewalls startup was successful. This value is intended to be the exit code of the process. + * @return zero if the firewalls startup was successful. This value is the exit code of the process. */ - fun run(): Boolean { + override fun runProgram(): Int { val startTime = System.currentTimeMillis() - val (argsParser, cmdlineOptions) = parseArguments() // We do the single firewall check before we initialise logging so that in case of a double-firewall start it // doesn't mess with the running firewall's logs. - enforceSingleBridgeIsRunning(cmdlineOptions.baseDirectory) + enforceSingleBridgeIsRunning(cmdLineOptions.baseDirectory) - initLogging(cmdlineOptions) + initLogging() val versionInfo = getVersionInfo() - if (cmdlineOptions.isVersion) { - println("${versionInfo.vendor} ${versionInfo.releaseVersion}") - println("Revision ${versionInfo.revision}") - println("Platform Version ${versionInfo.platformVersion}") - return true - } - - // Maybe render command line help. - if (cmdlineOptions.help) { - argsParser.printHelp(System.out) - return true - } val conf = try { - loadConfigFile(cmdlineOptions) + loadConfigFile() } catch (e: Exception) { log.error("Exception during firewall configuration", e) - return false + return ExitCodes.FAILURE } try { - logStartupInfo(versionInfo, cmdlineOptions, conf) + logStartupInfo(versionInfo, conf) } catch (e: Exception) { log.error("Exception during firewall registration", e) - return false + return ExitCodes.FAILURE } val firewall = try { - cmdlineOptions.baseDirectory.createDirectories() + cmdLineOptions.baseDirectory.createDirectories() startFirewall(conf, versionInfo, startTime) } catch (e: Exception) { if (e.message?.startsWith("Unknown named curve:") == true) { @@ -77,7 +67,7 @@ class FirewallStartup(val args: Array) { } else { log.error("Exception during firewall startup", e) } - return false + return ExitCodes.FAILURE } if (System.getProperties().containsKey("WAIT_KEY_FOR_EXIT")) { @@ -89,10 +79,10 @@ class FirewallStartup(val args: Array) { log.info("firewall shutting down") firewall.stop() - return true + return ExitCodes.SUCCESS } - fun logStartupInfo(versionInfo: FirewallVersionInfo, cmdlineOptions: CmdLineOptions, conf: FirewallConfiguration) { + fun logStartupInfo(versionInfo: FirewallVersionInfo, conf: FirewallConfiguration) { log.info("Vendor: ${versionInfo.vendor}") log.info("Release: ${versionInfo.releaseVersion}") log.info("Platform Version: ${versionInfo.platformVersion}") @@ -106,7 +96,7 @@ class FirewallStartup(val args: Array) { log.info("classpath: ${info.classPath}") log.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}") log.info("Machine: ${lookupMachineNameAndMaybeWarn()}") - log.info("Working Directory: ${cmdlineOptions.baseDirectory}") + log.info("Working Directory: ${cmdLineOptions.baseDirectory}") val agentProperties = VMSupport.getAgentProperties() if (agentProperties.containsKey("sun.jdwp.listenerAddress")) { log.info("Debug port: ${agentProperties.getProperty("sun.jdwp.listenerAddress")}") @@ -114,20 +104,16 @@ class FirewallStartup(val args: Array) { log.info("Starting as firewall mode of ${conf.firewallMode}") } - protected fun loadConfigFile(cmdlineOptions: CmdLineOptions): FirewallConfiguration = cmdlineOptions.loadConfig() + protected fun loadConfigFile(): FirewallConfiguration = cmdLineOptions.loadConfig() protected fun getVersionInfo(): FirewallVersionInfo { - // Manifest properties are only available if running from the corda jar - fun manifestValue(name: String): String? = if (Manifests.exists(name)) Manifests.read(name) else null - return FirewallVersionInfo( - manifestValue("Corda-Platform-Version")?.toInt() ?: 1, - manifestValue("Corda-Release-Version") ?: "Unknown", - manifestValue("Corda-Revision") ?: "Unknown", - manifestValue("Corda-Vendor") ?: "Unknown" + CordaVersionProvider.platformVersion, + CordaVersionProvider.releaseVersion, + CordaVersionProvider.revision, + CordaVersionProvider.vendor ) } - private fun enforceSingleBridgeIsRunning(baseDirectory: Path) { // Write out our process ID (which may or may not resemble a UNIX process id - to us it's just a string) to a // file that we'll do our best to delete on exit. But if we don't, it'll be overwritten next time. If it already @@ -171,25 +157,13 @@ class FirewallStartup(val args: Array) { return hostName } - private fun parseArguments(): Pair { - val argsParser = ArgsParser() - val cmdlineOptions = try { - argsParser.parse(*args) - } catch (ex: OptionException) { - println("Invalid command line arguments: ${ex.message}") - argsParser.printHelp(System.out) - exitProcess(1) - } - return Pair(argsParser, cmdlineOptions) - } - - fun initLogging(cmdlineOptions: CmdLineOptions) { - val loggingLevel = cmdlineOptions.loggingLevel.name.toLowerCase(Locale.ENGLISH) + override fun initLogging() { + val loggingLevel = loggingLevel.name.toLowerCase(Locale.ENGLISH) System.setProperty("defaultLogLevel", loggingLevel) // These properties are referenced from the XML config file. - if (cmdlineOptions.logToConsole) { + if (verbose) { System.setProperty("consoleLogLevel", loggingLevel) } - System.setProperty("log-path", (cmdlineOptions.baseDirectory / LOGS_DIRECTORY_NAME).toString()) + System.setProperty("log-path", (cmdLineOptions.baseDirectory / LOGS_DIRECTORY_NAME).toString()) SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler. SLF4JBridgeHandler.install() } diff --git a/bridge/src/test/kotlin/net/corda/bridge/BridgeTestHelper.kt b/bridge/src/test/kotlin/net/corda/bridge/BridgeTestHelper.kt index c0a8430910..d5c0f4b266 100644 --- a/bridge/src/test/kotlin/net/corda/bridge/BridgeTestHelper.kt +++ b/bridge/src/test/kotlin/net/corda/bridge/BridgeTestHelper.kt @@ -42,15 +42,14 @@ fun createNetworkParams(baseDirectory: Path): Int { fun createAndLoadConfigFromResource(baseDirectory: Path, configResource: String): FirewallConfiguration { val workspaceFolder = baseDirectory.normalize().toAbsolutePath() - val args = arrayOf("--base-directory", workspaceFolder.toString()) - val argsParser = ArgsParser() - val cmdlineOptions = argsParser.parse(*args) - val configFile = cmdlineOptions.configFile + val cmdLineOptions = FirewallCmdLineOptions() + cmdLineOptions.baseDirectory = workspaceFolder + val configFile = cmdLineOptions.configFile configFile.normalize().parent?.createDirectories() ConfigTest::class.java.getResourceAsStream(configResource).use { Files.copy(it, configFile) } - val config = cmdlineOptions.loadConfig() + val config = cmdLineOptions.loadConfig() return config } diff --git a/docs/source/cli-application-shell-extensions.rst b/docs/source/cli-application-shell-extensions.rst index 4c81ea6c07..37173b975f 100644 --- a/docs/source/cli-application-shell-extensions.rst +++ b/docs/source/cli-application-shell-extensions.rst @@ -74,3 +74,11 @@ List of existing CLI applications | :doc:`Blob inspector` | ``corda-tools-blob-inspector-.jar`` | ``blob-inspector --