mirror of
https://github.com/corda/corda.git
synced 2025-02-21 09:51:57 +00:00
CORDA-1838: Add subcommands to node (#4091)
* Tidy up * Add install-shell-extensions command * Make cli tests use same version of picocli as everything else * Remove initLogging from NodeStartup, it is ran earlier by CordaCLIWrapper * Use picocli snapshot for testing * Use RunLast() parser to invoke correct subcommands * Deprecate old clear-network-map-cache parameter * Restructure NodeStartup for commands * Get rid of -c option since the flag method has been deprecated and that didn't exist in last release * Update documentation * Update backwards compatibility test * Get all subcommands working * Refactor sub commands into seperate classes * Update docs and fix some tests * Docs changes * Fix merge conflicts with master * Fix renamed parameters * Fix test failure * Fix compatibility tests * Add missing compatibility test for blob inspector * Remove blob inspector compatibility test as there are import conflicts * Assorted doc fixes * Addressing review comments * More review comments * Couple more bits * Fix broken tests * Fix compilation error * More merge conflicts * Make startup logging function a bit more sensible * Fix broken shell extensions * Make shell extensions work with subcommands * Make sure parameters for deprecated options are carried through * More review comments * Adding some s's * One last go * Fix compilation error on Windows * Revert logging changes * Revert docs back to their original imperatively moody state
This commit is contained in:
parent
7e3aa7f30c
commit
0ab644783e
@ -70,7 +70,7 @@ buildscript {
|
||||
ext.snappy_version = '0.4'
|
||||
ext.class_graph_version = '4.2.12'
|
||||
ext.jcabi_manifests_version = '1.1'
|
||||
ext.picocli_version = '3.5.2'
|
||||
ext.picocli_version = '3.6.1'
|
||||
|
||||
// Name of the IntelliJ SDK created for the deterministic Java rt.jar.
|
||||
// ext.deterministic_idea_sdk = '1.8 (Deterministic)'
|
||||
|
@ -98,9 +98,9 @@ The blob inspector can be started with the following command-line options:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
blob-inspector [-hvV] [--full-parties] [--install-shell-extensions] [--schema]
|
||||
[--format=type] [--input-format=type]
|
||||
[--logging-level=<loggingLevel>] [SOURCE]
|
||||
blob-inspector [-hvV] [--full-parties] [--schema] [--format=type]
|
||||
[--input-format=type] [--logging-level=<loggingLevel>] SOURCE
|
||||
[COMMAND]
|
||||
|
||||
* ``--format=type``: Output format. Possible values: [YAML, JSON]. Default: YAML.
|
||||
* ``--input-format=type``: Input format. If the file can't be decoded with the given value it's auto-detected, so you should
|
||||
@ -109,6 +109,10 @@ The blob inspector can be started with the following command-line options:
|
||||
* ``--schema``: Print the blob's schema first.
|
||||
* ``--verbose``, ``--log-to-console``, ``-v``: If set, prints logging to the console as well as to a file.
|
||||
* ``--logging-level=<loggingLevel>``: Enable logging at this level and higher. Possible values: ERROR, WARN, INFO, DEBUG, TRACE. Default: INFO.
|
||||
* ``--install-shell-extensions``: Install ``blob-inspector`` alias and auto completion for bash and zsh. See :doc:`cli-application-shell-extensions` for more info.
|
||||
* ``--help``, ``-h``: Show this help message and exit.
|
||||
* ``--version``, ``-V``: Print version information and exit.
|
||||
* ``--version``, ``-V``: Print version information and exit.
|
||||
|
||||
Sub-commands
|
||||
^^^^^^^^^^^^
|
||||
|
||||
``install-shell-extensions``: Install ``blob-inspector`` alias and auto completion for bash and zsh. See :doc:`cli-application-shell-extensions` for more info.
|
@ -1548,9 +1548,9 @@ New features in this release:
|
||||
|
||||
* Testnet
|
||||
|
||||
* Permissioning infrastructure phase one is built out. The node now has a notion of developer mode vs normal
|
||||
mode. In developer mode it works like M3 and the SSL certificates used by nodes running on your local
|
||||
machine all self-sign using a developer key included in the source tree. When developer mode is not active,
|
||||
* Permissioning infrastructure phase one is built out. The node now has a notion of development mode vs normal
|
||||
mode. In development mode it works like M3 and the SSL certificates used by nodes running on your local
|
||||
machine all self-sign using a developer key included in the source tree. When development mode is not active,
|
||||
the node won't start until it has a signed certificate. Such a certificate can be obtained by simply running
|
||||
an included command line utility which generates a CSR and submits it to a permissioning service, then waits
|
||||
for the signed certificate to be returned. Note that currently there is no public Corda testnet, so we are
|
||||
|
@ -10,7 +10,7 @@ Users of ``bash`` or ``zsh`` can install an alias and auto-completion for Corda
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
java -jar <name-of-JAR>.jar --install-shell-extensions
|
||||
java -jar <name-of-JAR>.jar install-shell-extensions
|
||||
|
||||
Then, either restart your shell, or for ``bash`` users run:
|
||||
|
||||
@ -34,7 +34,7 @@ For example, for the Corda node, install the shell extensions using
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
java -jar corda-<version>.jar --install-shell-extensions
|
||||
java -jar corda-<version>.jar install-shell-extensions
|
||||
|
||||
And then run the node by running:
|
||||
|
||||
|
@ -50,8 +50,11 @@ Standard options
|
||||
* A ``--logging-level`` option should be provided which specifies the logging level to be used in any logging files. Acceptable values should be ``DEBUG``, ``TRACE``, ``INFO``, ``WARN`` and ``ERROR``.
|
||||
* ``--verbose`` and ``--log-to-console`` options should be provided (both equivalent) which specifies that logging output should be displayed in the console.
|
||||
A ``-v`` short option should also be provided.
|
||||
* A ``--install-shell-extensions`` option should be provided that creates and installs a bash completion file.
|
||||
|
||||
Standard subcommands
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* An ``install-shell-extensions`` subcommand should be provided that creates and installs a bash completion file.
|
||||
|
||||
Defaults
|
||||
~~~~~~~~
|
||||
@ -94,7 +97,7 @@ In order to use it, create a class containing your command line options using th
|
||||
}
|
||||
|
||||
class UsefulUtility : CordaCliWrapper(
|
||||
"useful-utility", // the alias to be used for this utility in bash. When --install-shell-extensions is run
|
||||
"useful-utility", // the alias to be used for this utility in bash. When install-shell-extensions is run
|
||||
// you will be able to invoke this command by running <useful-utility --opts> from the command line
|
||||
"A command line utility that is super useful!" // A description of this utility to be displayed when --help is run
|
||||
) {
|
||||
|
@ -125,7 +125,7 @@ absolute path to the node's base directory.
|
||||
.. note:: The RPC SSL certificate is used by RPC clients to authenticate the connection.
|
||||
The Node operator must provide RPC clients with a truststore containing the certificate they can trust.
|
||||
We advise Node operators to not use the P2P keystore for RPC.
|
||||
The node ships with a command line argument "--just-generate-rpc-ssl-settings", which generates a secure keystore
|
||||
The node can be run with the "generate-rpc-ssl-settings" command, which generates a secure keystore
|
||||
and truststore that can be used to secure the RPC connection. You can use this if you have no special requirements.
|
||||
|
||||
|
||||
|
@ -255,14 +255,19 @@ The network bootstrapper can be started with the following command-line options:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
bootstrapper [-hvV] [--install-shell-extensions] [--no-copy] [--dir=<dir>]
|
||||
[--logging-level=<loggingLevel>]
|
||||
bootstrapper [-hvV] [--no-copy] [--dir=<dir>] [--logging-level=<loggingLevel>]
|
||||
[--minimum-platform-version=<minimumPlatformVersion>] [COMMAND]
|
||||
|
||||
* ``--dir=<dir>``: Root directory containing the node configuration files and CorDapp JARs that will form the test network.
|
||||
It may also contain existing node directories. Defaults to the current directory.
|
||||
* ``--no-copy``: Don't copy the CorDapp JARs into the nodes' "cordapps" directories.
|
||||
* ``--verbose``, ``--log-to-console``, ``-v``: If set, prints logging to the console as well as to a file.
|
||||
* ``--logging-level=<loggingLevel>``: Enable logging at this level and higher. Possible values: ERROR, WARN, INFO, DEBUG, TRACE. Default: INFO.
|
||||
* ``--install-shell-extensions``: Install ``bootstrapper`` alias and auto completion for bash and zsh. See :doc:`cli-application-shell-extensions` for more info.
|
||||
* ``--help``, ``-h``: Show this help message and exit.
|
||||
* ``--version``, ``-V``: Print version information and exit.
|
||||
* ``--version``, ``-V``: Print version information and exit.
|
||||
* ``--minimum-platform-version``: The minimum platform version to use in the generated network-parameters.
|
||||
|
||||
Sub-commands
|
||||
^^^^^^^^^^^^
|
||||
|
||||
``install-shell-extensions``: Install ``bootstrapper`` alias and auto completion for bash and zsh. See :doc:`cli-application-shell-extensions` for more info.
|
@ -63,7 +63,7 @@ be used to supplement or replace the HTTP network map. If the same node is adver
|
||||
latest one is taken.
|
||||
|
||||
On startup the node generates its own signed node info file, filename of the format ``nodeInfo-${hash}``. It can also be
|
||||
generated using the ``--just-generate-node-info`` command line flag without starting the node. To create a simple network
|
||||
generated using the ``generate-node-info`` sub-command without starting the node. To create a simple network
|
||||
without the HTTP network map service simply place this file in the ``additional-node-infos`` directory of every node that's
|
||||
part of this network. For example, a simple way to do this is to use rsync.
|
||||
|
||||
@ -192,7 +192,7 @@ you either need to run from the command line:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
java -jar corda.jar --clear-network-map-cache
|
||||
java -jar corda.jar clear-network-cache
|
||||
|
||||
or call RPC method `clearNetworkMapCache` (it can be invoked through the node's shell as `run clearNetworkMapCache`, for more information on
|
||||
how to log into node's shell see :doc:`shell`). As we are testing and hardening the implementation this step shouldn't be required.
|
||||
|
@ -48,25 +48,38 @@ Command-line options
|
||||
The node can optionally be started with the following command-line options:
|
||||
|
||||
* ``--base-directory``, ``-b``: The node working directory where all the files are kept (default: ``.``).
|
||||
* ``--clear-network-map-cache``, ``-c``: Clears local copy of network map, on node startup it will be restored from server or file system.
|
||||
* ``--config-file``, ``-f``: The path to the config file. Defaults to ``node.conf``.
|
||||
* ``--dev-mode``, ``-d``: Runs the node in developer mode. Unsafe in production. Defaults to true on MacOS and desktop versions of Windows. False otherwise.
|
||||
* ``--initial-registration``: Start initial node registration with the compatibility zone to obtain a certificate from the Doorman.
|
||||
* ``--just-generate-node-info``: Perform the node start-up task necessary to generate its nodeInfo, save it to disk, then
|
||||
quit.
|
||||
* ``--just-generate-rpc-ssl-settings``: Generate the ssl keystore and truststore for a secure RPC connection.
|
||||
* ``--network-root-truststore``, ``-t``: Network root trust store obtained from network operator.
|
||||
* ``--network-root-truststore-password``, ``-p``: Network root trust store password obtained from network operator.
|
||||
* ``--dev-mode``, ``-d``: Runs the node in development mode. Unsafe in production. Defaults to true on MacOS and desktop versions of Windows. False otherwise.
|
||||
* ``--no-local-shell``, ``-n``: Do not start the embedded shell locally.
|
||||
* ``--on-unknown-config-keys <[FAIL,WARN,INFO]>``: How to behave on unknown node configuration. Defaults to FAIL.
|
||||
* ``--sshd``: Enables SSH server for node administration.
|
||||
* ``--sshd-port``: Sets the port for the SSH server. If not supplied and SSH server is enabled, the port defaults to 2222.
|
||||
* ``--verbose``, ``--log-to-console``, ``-v``: If set, prints logging to the console as well as to a file.
|
||||
* ``--logging-level=<loggingLevel>``: Enable logging at this level and higher. Possible values: ERROR, WARN, INFO, DEBUG, TRACE. Default: INFO.
|
||||
* ``--install-shell-extensions``: Install ``corda`` alias and auto completion for bash and zsh. See :doc:`cli-application-shell-extensions` for more info.
|
||||
* ``--help``, ``-h``: Show this help message and exit.
|
||||
* ``--version``, ``-V``: Print version information and exit.
|
||||
|
||||
Sub-commands
|
||||
^^^^^^^^^^^^
|
||||
|
||||
``bootstrap-raft-cluster``: Bootstraps Raft cluster. The node forms a single node cluster (ignoring otherwise configured peer
|
||||
addresses), acting as a seed for other nodes to join the cluster.
|
||||
|
||||
``clear-network-cache``: Clears local copy of network map, on node startup it will be restored from server or file system.
|
||||
|
||||
``initial-registration``: Starts initial node registration with the compatibility zone to obtain a certificate from the Doorman.
|
||||
|
||||
Parameters:
|
||||
|
||||
* ``--network-root-truststore``, ``-t`` **required**: Network root trust store obtained from network operator.
|
||||
* ``--network-root-truststore-password``, ``-p``: Network root trust store password obtained from network operator.
|
||||
|
||||
``generate-node-info``: Performs the node start-up tasks necessary to generate the nodeInfo file, saves it to disk, then exits.
|
||||
|
||||
``generate-rpc-ssl-settings``: Generates the SSL keystore and truststore for a secure RPC connection.
|
||||
|
||||
``install-shell-extensions``: Install ``corda`` alias and auto completion for bash and zsh. See :doc:`cli-application-shell-extensions` for more info.
|
||||
|
||||
.. _enabling-remote-debugging:
|
||||
|
||||
Enabling remote debugging
|
||||
|
@ -105,28 +105,15 @@ Starting the standalone shell
|
||||
|
||||
Run the following command from the terminal:
|
||||
|
||||
Linux and MacOS
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
.. code:: bash
|
||||
|
||||
java -jar corda-tools-shell-cli-VERSION_NUMBER.jar [--config-file PATH | --cordpass-directory PATH --commands-directory PATH --host HOST --port PORT
|
||||
--user USER --password PASSWORD --sshd-port PORT --sshd-hostkey-directory PATH --keystore-password PASSWORD
|
||||
--keystore-file FILE --truststore-password PASSWORD --truststore-file FILE | --help]
|
||||
|
||||
Windows
|
||||
^^^^^^^
|
||||
|
||||
.. code:: bash
|
||||
|
||||
corda-shell [-hvV] [--install-shell-extensions]
|
||||
[--logging-level=<loggingLevel>] [--password=<password>]
|
||||
corda-shell [-hvV] [--logging-level=<loggingLevel>] [--password=<password>]
|
||||
[--sshd-hostkey-directory=<sshdHostKeyDirectory>]
|
||||
[--sshd-port=<sshdPort>] [--truststore-file=<trustStoreFile>]
|
||||
[--truststore-password=<trustStorePassword>]
|
||||
[--truststore-type=<trustStoreType>] [--user=<user>] [-a=<host>]
|
||||
[-c=<cordappDirectory>] [-f=<configFile>] [-o=<commandsDirectory>]
|
||||
[-p=<port>]
|
||||
[-p=<port>] [COMMAND]
|
||||
|
||||
Where:
|
||||
|
||||
@ -144,10 +131,11 @@ Where:
|
||||
* ``--truststore-type=<trustStoreType>``: The type of the TrustStore (e.g. JKS).
|
||||
* ``--verbose``, ``--log-to-console``, ``-v``: If set, prints logging to the console as well as to a file.
|
||||
* ``--logging-level=<loggingLevel>``: Enable logging at this level and higher. Possible values: ERROR, WARN, INFO, DEBUG, TRACE. Default: INFO.
|
||||
* ``--install-shell-extensions``: Install ``corda-shell`` alias and auto completion for bash and zsh. See :doc:`cli-application-shell-extensions` for more info.
|
||||
* ``--help``, ``-h``: Show this help message and exit.
|
||||
* ``--version``, ``-V``: Print version information and exit.
|
||||
|
||||
Additionally, the ``install-shell-extensions`` subcommand can be used to install the ``corda-shell`` alias and auto completion for bash and zsh. See :doc:`cli-application-shell-extensions` for more info.
|
||||
|
||||
The format of ``config-file``:
|
||||
|
||||
.. code:: bash
|
||||
|
@ -65,7 +65,7 @@ internal constructor(private val initSerEnv: Boolean,
|
||||
"java",
|
||||
"-jar",
|
||||
"corda.jar",
|
||||
"--just-generate-node-info"
|
||||
"generate-node-info"
|
||||
)
|
||||
|
||||
private const val LOGS_DIR_NAME = "logs"
|
||||
|
@ -64,7 +64,7 @@ class FlowOverrideTests {
|
||||
private val nodeBClasses = setOf(Ping::class.java, Pong::class.java)
|
||||
|
||||
@Test
|
||||
fun `should use the most "specific" implementation of a responding flow`() {
|
||||
fun `should use the most specific implementation of a responding flow`() {
|
||||
driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = emptySet())) {
|
||||
val nodeA = startNode(additionalCordapps = setOf(cordappForClasses(*nodeAClasses.toTypedArray()))).getOrThrow()
|
||||
val nodeB = startNode(additionalCordapps = setOf(cordappForClasses(*nodeBClasses.toTypedArray()))).getOrThrow()
|
||||
|
@ -4,11 +4,11 @@
|
||||
package net.corda.node
|
||||
|
||||
import net.corda.cliutils.start
|
||||
import net.corda.node.internal.NodeStartup
|
||||
import net.corda.node.internal.NodeStartupCli
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
// Pass the arguments to the Node factory. In the Enterprise edition, this line is modified to point to a subclass.
|
||||
// It will exit the process in case of startup failure and is not intended to be used by embedders. If you want
|
||||
// to embed Node in your own container, instantiate it directly and set up the configuration objects yourself.
|
||||
NodeStartup().start(args)
|
||||
NodeStartupCli().start(args)
|
||||
}
|
||||
|
@ -2,18 +2,18 @@ package net.corda.node
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.utilities.Try
|
||||
import net.corda.node.services.config.ConfigHelper
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.NodeConfigurationImpl
|
||||
import net.corda.node.services.config.parseAsNodeConfiguration
|
||||
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
|
||||
import picocli.CommandLine.Option
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
class NodeCmdLineOptions {
|
||||
open class SharedNodeCmdLineOptions {
|
||||
@Option(
|
||||
names = ["-b", "--base-directory"],
|
||||
description = ["The node working directory where all the files are kept."]
|
||||
@ -27,6 +27,53 @@ class NodeCmdLineOptions {
|
||||
private var _configFile: Path? = null
|
||||
val configFile: Path get() = _configFile ?: (baseDirectory / "node.conf")
|
||||
|
||||
@Option(
|
||||
names = ["--on-unknown-config-keys"],
|
||||
description = ["How to behave on unknown node configuration. \${COMPLETION-CANDIDATES}"]
|
||||
)
|
||||
var unknownConfigKeysPolicy: UnknownConfigKeysPolicy = UnknownConfigKeysPolicy.FAIL
|
||||
|
||||
@Option(
|
||||
names = ["-d", "--dev-mode"],
|
||||
description = ["Runs the node in development mode. Unsafe for production."]
|
||||
)
|
||||
var devMode: Boolean? = null
|
||||
|
||||
open fun loadConfig(): NodeConfiguration {
|
||||
return getRawConfig().parseAsNodeConfiguration(unknownConfigKeysPolicy::handle)
|
||||
}
|
||||
|
||||
protected fun getRawConfig(): Config {
|
||||
val rawConfig = ConfigHelper.loadConfig(
|
||||
baseDirectory,
|
||||
configFile
|
||||
)
|
||||
if (devMode == true) {
|
||||
println("Config:\n${rawConfig.root().render(ConfigRenderOptions.defaults())}")
|
||||
}
|
||||
return rawConfig
|
||||
}
|
||||
|
||||
fun copyFrom(other: SharedNodeCmdLineOptions) {
|
||||
baseDirectory = other.baseDirectory
|
||||
_configFile = other._configFile
|
||||
unknownConfigKeysPolicy= other.unknownConfigKeysPolicy
|
||||
devMode = other.devMode
|
||||
}
|
||||
}
|
||||
|
||||
class InitialRegistrationCmdLineOptions : SharedNodeCmdLineOptions() {
|
||||
override fun loadConfig(): NodeConfiguration {
|
||||
return getRawConfig().parseAsNodeConfiguration(unknownConfigKeysPolicy::handle).also { config ->
|
||||
require(!config.devMode) { "Registration cannot occur in development mode" }
|
||||
require(config.compatibilityZoneURL != null || config.networkServices != null) {
|
||||
"compatibilityZoneURL or networkServices must be present in the node configuration file in registration mode."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
|
||||
@Option(
|
||||
names = ["--sshd"],
|
||||
description = ["If set, enables SSH server for node administration."]
|
||||
@ -45,84 +92,66 @@ class NodeCmdLineOptions {
|
||||
)
|
||||
var noLocalShell: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["--initial-registration"],
|
||||
description = ["Start initial node registration with Corda network to obtain certificate from the permissioning server."]
|
||||
)
|
||||
var isRegistration: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["-t", "--network-root-truststore"],
|
||||
description = ["Network root trust store obtained from network operator."]
|
||||
)
|
||||
private var _networkRootTrustStorePath: Path? = null
|
||||
val networkRootTrustStorePath: Path get() = _networkRootTrustStorePath ?: baseDirectory / "certificates" / "network-root-truststore.jks"
|
||||
|
||||
@Option(
|
||||
names = ["-p", "--network-root-truststore-password"],
|
||||
description = ["Network root trust store password obtained from network operator."]
|
||||
)
|
||||
var networkRootTrustStorePassword: String? = null
|
||||
|
||||
@Option(
|
||||
names = ["--on-unknown-config-keys"],
|
||||
description = ["How to behave on unknown node configuration. \${COMPLETION-CANDIDATES}"]
|
||||
)
|
||||
var unknownConfigKeysPolicy: UnknownConfigKeysPolicy = UnknownConfigKeysPolicy.FAIL
|
||||
|
||||
@Option(
|
||||
names = ["-d", "--dev-mode"],
|
||||
description = ["Run the node in developer mode. Unsafe for production."]
|
||||
)
|
||||
var devMode: Boolean? = null
|
||||
|
||||
@Option(
|
||||
names = ["--just-generate-node-info"],
|
||||
description = ["Perform the node start-up task necessary to generate its node info, save it to disk, then quit"]
|
||||
description = ["DEPRECATED. Performs the node start-up tasks necessary to generate the nodeInfo file, saves it to disk, then exits."],
|
||||
hidden = true
|
||||
)
|
||||
var justGenerateNodeInfo: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["--just-generate-rpc-ssl-settings"],
|
||||
description = ["Generate the SSL key and trust stores for a secure RPC connection."]
|
||||
description = ["DEPRECATED. Generates the SSL key and trust stores for a secure RPC connection."],
|
||||
hidden = true
|
||||
)
|
||||
var justGenerateRpcSslCerts: Boolean = false
|
||||
|
||||
@Option(
|
||||
names = ["-c", "--clear-network-map-cache"],
|
||||
description = ["Clears local copy of network map, on node startup it will be restored from server or file system."]
|
||||
names = ["--clear-network-map-cache"],
|
||||
description = ["DEPRECATED. Clears local copy of network map, on node startup it will be restored from server or file system."],
|
||||
hidden = true
|
||||
)
|
||||
var clearNetworkMapCache: Boolean = false
|
||||
|
||||
val nodeRegistrationOption: NodeRegistrationOption? by lazy {
|
||||
if (isRegistration) {
|
||||
requireNotNull(networkRootTrustStorePassword) { "Network root trust store password must be provided in registration mode using --network-root-truststore-password." }
|
||||
require(networkRootTrustStorePath.exists()) { "Network root trust store path: '$networkRootTrustStorePath' doesn't exist" }
|
||||
NodeRegistrationOption(networkRootTrustStorePath, networkRootTrustStorePassword!!)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
@Option(
|
||||
names = ["--initial-registration"],
|
||||
description = ["DEPRECATED. Starts initial node registration with Corda network to obtain certificate from the permissioning server."],
|
||||
hidden = true
|
||||
)
|
||||
var isRegistration: Boolean = false
|
||||
|
||||
fun loadConfig(): Pair<Config, Try<NodeConfiguration>> {
|
||||
@Option(
|
||||
names = ["-t", "--network-root-truststore"],
|
||||
description = ["DEPRECATED. Network root trust store obtained from network operator."],
|
||||
hidden = true
|
||||
)
|
||||
var networkRootTrustStorePathParameter: Path? = null
|
||||
|
||||
@Option(
|
||||
names = ["-p", "--network-root-truststore-password"],
|
||||
description = ["DEPRECATED. Network root trust store password obtained from network operator."],
|
||||
hidden = true
|
||||
)
|
||||
var networkRootTrustStorePassword: String? = null
|
||||
|
||||
override fun loadConfig(): NodeConfiguration {
|
||||
val rawConfig = ConfigHelper.loadConfig(
|
||||
baseDirectory,
|
||||
configFile,
|
||||
configOverrides = ConfigFactory.parseMap(mapOf("noLocalShell" to this.noLocalShell) +
|
||||
if (sshdServer) mapOf("sshd" to mapOf("port" to sshdServerPort.toString())) else emptyMap<String, Any>() +
|
||||
if (devMode != null) mapOf("devMode" to this.devMode) else emptyMap())
|
||||
if (devMode != null) mapOf("devMode" to this.devMode) else emptyMap())
|
||||
)
|
||||
return rawConfig to Try.on {
|
||||
rawConfig.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle).also { config ->
|
||||
if (nodeRegistrationOption != null) {
|
||||
require(!config.devMode) { "Registration cannot occur in devMode" }
|
||||
require(config.compatibilityZoneURL != null || config.networkServices != null) {
|
||||
"compatibilityZoneURL or networkServices must be present in the node configuration file in registration mode."
|
||||
}
|
||||
return rawConfig.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle).also { config ->
|
||||
if (isRegistration) {
|
||||
require(!config.devMode) { "Registration cannot occur in development mode" }
|
||||
require(config.compatibilityZoneURL != null || config.networkServices != null) {
|
||||
"compatibilityZoneURL or networkServices must be present in the node configuration file in registration mode."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class NodeRegistrationOption(val networkRootTrustStorePath: Path, val networkRootTrustStorePassword: String)
|
||||
|
@ -661,7 +661,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
requireNotNull(getCertificateStores()) {
|
||||
"One or more keyStores (identity or TLS) or trustStore not found. " +
|
||||
"Please either copy your existing keys and certificates from another node, " +
|
||||
"or if you don't have one yet, fill out the config file and run corda.jar --initial-registration. " +
|
||||
"or if you don't have one yet, fill out the config file and run corda.jar initial-registration. " +
|
||||
"Read more at: https://docs.corda.net/permissioning.html"
|
||||
}
|
||||
} catch (e: KeyStoreException) {
|
||||
|
@ -6,6 +6,7 @@ import com.codahale.metrics.MetricRegistry
|
||||
import com.palominolabs.metrics.newrelic.AllEnabledMetricAttributeFilter
|
||||
import com.palominolabs.metrics.newrelic.NewRelicReporter
|
||||
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||
import net.corda.cliutils.ShellConstants
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
@ -110,9 +111,13 @@ open class Node(configuration: NodeConfiguration,
|
||||
LoggerFactory.getLogger(loggerName).info(msg)
|
||||
}
|
||||
|
||||
fun printInRed(message: String) {
|
||||
println("${ShellConstants.RED}$message${ShellConstants.RESET}")
|
||||
}
|
||||
|
||||
fun printWarning(message: String) {
|
||||
Emoji.renderIfSupported {
|
||||
println("${Emoji.warningSign} ATTENTION: $message")
|
||||
printInRed("${Emoji.warningSign} ATTENTION: $message")
|
||||
}
|
||||
staticLog.warn(message)
|
||||
}
|
||||
@ -132,13 +137,13 @@ open class Node(configuration: NodeConfiguration,
|
||||
// TODO: make this configurable.
|
||||
const val MAX_RPC_MESSAGE_SIZE = 10485760
|
||||
|
||||
fun isValidJavaVersion(): Boolean {
|
||||
fun isInvalidJavaVersion(): Boolean {
|
||||
if (!hasMinimumJavaVersion()) {
|
||||
println("You are using a version of Java that is not supported (${SystemUtils.JAVA_VERSION}). Please upgrade to the latest version of Java 8.")
|
||||
println("Corda will now exit...")
|
||||
return false
|
||||
return true
|
||||
}
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
private fun hasMinimumJavaVersion(): Boolean {
|
||||
|
@ -1,31 +1,24 @@
|
||||
package net.corda.node.internal
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigException
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import io.netty.channel.unix.Errors
|
||||
import net.corda.cliutils.CordaCliWrapper
|
||||
import net.corda.cliutils.CordaVersionProvider
|
||||
import net.corda.cliutils.ExitCodes
|
||||
import net.corda.cliutils.*
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.internal.concurrent.thenMatch
|
||||
import net.corda.core.internal.cordapp.CordappImpl
|
||||
import net.corda.core.internal.errors.AddressBindingException
|
||||
import net.corda.core.utilities.Try
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.*
|
||||
import net.corda.node.internal.Node.Companion.isValidJavaVersion
|
||||
import net.corda.node.internal.Node.Companion.isInvalidJavaVersion
|
||||
import net.corda.node.internal.cordapp.MultipleCordappsForFlowException
|
||||
import net.corda.node.internal.subcommands.*
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.shouldStartLocalShell
|
||||
import net.corda.node.services.config.shouldStartSSHDaemon
|
||||
import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate
|
||||
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
|
||||
import net.corda.node.utilities.registration.NodeRegistrationException
|
||||
import net.corda.node.utilities.registration.NodeRegistrationHelper
|
||||
import net.corda.node.utilities.saveToKeyStore
|
||||
import net.corda.node.utilities.saveToTrustStore
|
||||
import net.corda.nodeapi.internal.addShutdownHook
|
||||
import net.corda.nodeapi.internal.config.UnknownConfigurationKeysException
|
||||
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
|
||||
@ -33,10 +26,8 @@ import net.corda.nodeapi.internal.persistence.DatabaseIncompatibleException
|
||||
import net.corda.tools.shell.InteractiveShell
|
||||
import org.fusesource.jansi.Ansi
|
||||
import org.slf4j.bridge.SLF4JBridgeHandler
|
||||
import picocli.CommandLine.Mixin
|
||||
import picocli.CommandLine.*
|
||||
import sun.misc.VMSupport
|
||||
import java.io.Console
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.RandomAccessFile
|
||||
import java.lang.management.ManagementFactory
|
||||
@ -45,201 +36,145 @@ import java.nio.file.Path
|
||||
import java.time.DayOfWeek
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.*
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/** This class is responsible for starting a Node from command line arguments. */
|
||||
open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
|
||||
/** An interface that can be implemented to tell the node what to do once it's intitiated. */
|
||||
interface RunAfterNodeInitialisation {
|
||||
fun run(node: Node)
|
||||
}
|
||||
|
||||
/** Base class for subcommands to derive from that initialises the logs and provides standard options. */
|
||||
abstract class NodeCliCommand(alias: String, description: String, val startup: NodeStartup) : CliWrapperBase(alias, description), NodeStartupLogging {
|
||||
companion object {
|
||||
private val logger by lazy { loggerFor<Node>() } // I guess this is lazy to allow for logging init, but why Node?
|
||||
const val LOGS_DIRECTORY_NAME = "logs"
|
||||
const val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in"
|
||||
private const val INITIAL_REGISTRATION_MARKER = ".initialregistration"
|
||||
}
|
||||
|
||||
override fun initLogging() = this.initLogging(cmdLineOptions.baseDirectory)
|
||||
|
||||
@Mixin
|
||||
val cmdLineOptions = SharedNodeCmdLineOptions()
|
||||
}
|
||||
|
||||
/** Main corda entry point. */
|
||||
open class NodeStartupCli : CordaCliWrapper("corda", "Runs a Corda Node") {
|
||||
val startup = NodeStartup()
|
||||
private val networkCacheCli = ClearNetworkCacheCli(startup)
|
||||
private val justGenerateNodeInfoCli = GenerateNodeInfoCli(startup)
|
||||
private val justGenerateRpcSslCertsCli = GenerateRpcSslCertsCli(startup)
|
||||
private val initialRegistrationCli = InitialRegistrationCli(startup)
|
||||
|
||||
override fun initLogging() = this.initLogging(cmdLineOptions.baseDirectory)
|
||||
|
||||
override fun additionalSubCommands() = setOf(networkCacheCli, justGenerateNodeInfoCli, justGenerateRpcSslCertsCli, initialRegistrationCli)
|
||||
|
||||
override fun runProgram(): Int {
|
||||
return when {
|
||||
InitialRegistration.checkRegistrationMode(cmdLineOptions.baseDirectory) -> {
|
||||
println("Node was started before in `initial-registration` mode, but the registration was not completed.\nResuming registration.")
|
||||
initialRegistrationCli.cmdLineOptions.copyFrom(cmdLineOptions)
|
||||
initialRegistrationCli.runProgram()
|
||||
}
|
||||
//deal with legacy flags and redirect to subcommands
|
||||
cmdLineOptions.isRegistration -> {
|
||||
Node.printWarning("The --initial-registration flag has been deprecated and will be removed in a future version. Use the initial-registration command instead.")
|
||||
requireNotNull(cmdLineOptions.networkRootTrustStorePassword) { "Network root trust store password must be provided in registration mode using --network-root-truststore-password." }
|
||||
initialRegistrationCli.networkRootTrustStorePassword = cmdLineOptions.networkRootTrustStorePassword!!
|
||||
initialRegistrationCli.networkRootTrustStorePathParameter = cmdLineOptions.networkRootTrustStorePathParameter
|
||||
initialRegistrationCli.cmdLineOptions.copyFrom(cmdLineOptions)
|
||||
initialRegistrationCli.runProgram()
|
||||
}
|
||||
cmdLineOptions.clearNetworkMapCache -> {
|
||||
Node.printWarning("The --clear-network-map-cache flag has been deprecated and will be removed in a future version. Use the clear-network-cache command instead.")
|
||||
networkCacheCli.cmdLineOptions.copyFrom(cmdLineOptions)
|
||||
networkCacheCli.runProgram()
|
||||
}
|
||||
cmdLineOptions.justGenerateNodeInfo -> {
|
||||
Node.printWarning("The --just-generate-node-info flag has been deprecated and will be removed in a future version. Use the generate-node-info command instead.")
|
||||
justGenerateNodeInfoCli.cmdLineOptions.copyFrom(cmdLineOptions)
|
||||
justGenerateNodeInfoCli.runProgram()
|
||||
}
|
||||
cmdLineOptions.justGenerateRpcSslCerts -> {
|
||||
Node.printWarning("The --just-generate-rpc-ssl-settings flag has been deprecated and will be removed in a future version. Use the generate-rpc-ssl-settings command instead.")
|
||||
justGenerateRpcSslCertsCli.cmdLineOptions.copyFrom(cmdLineOptions)
|
||||
justGenerateRpcSslCertsCli.runProgram()
|
||||
}
|
||||
else -> startup.initialiseAndRun(cmdLineOptions, object : RunAfterNodeInitialisation {
|
||||
val startupTime = System.currentTimeMillis()
|
||||
override fun run(node: Node) = startup.startNode(node, startupTime)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Mixin
|
||||
val cmdLineOptions = NodeCmdLineOptions()
|
||||
}
|
||||
|
||||
/** This class provides a common set of functionality for starting a Node from command line arguments. */
|
||||
open class NodeStartup : NodeStartupLogging {
|
||||
companion object {
|
||||
private val logger by lazy { loggerFor<Node>() } // I guess this is lazy to allow for logging init, but why Node?
|
||||
const val LOGS_DIRECTORY_NAME = "logs"
|
||||
const val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in"
|
||||
}
|
||||
|
||||
lateinit var cmdLineOptions: SharedNodeCmdLineOptions
|
||||
|
||||
fun initialiseAndRun(cmdLineOptions: SharedNodeCmdLineOptions, afterNodeInitialisation: RunAfterNodeInitialisation): Int {
|
||||
this.cmdLineOptions = cmdLineOptions
|
||||
|
||||
/**
|
||||
* @return exit code based on the success of the node startup. This value is intended to be the exit code of the process.
|
||||
*/
|
||||
override fun runProgram(): Int {
|
||||
val startTime = System.currentTimeMillis()
|
||||
// Step 1. Check for supported Java version.
|
||||
if (!isValidJavaVersion()) return ExitCodes.FAILURE
|
||||
if (isInvalidJavaVersion()) return ExitCodes.FAILURE
|
||||
|
||||
// Step 2. We do the single node check before we initialise logging so that in case of a double-node start it
|
||||
// doesn't mess with the running node's logs.
|
||||
enforceSingleNodeIsRunning(cmdLineOptions.baseDirectory)
|
||||
|
||||
// Step 3. Initialise logging.
|
||||
initLogging()
|
||||
|
||||
// Step 4. Register all cryptography [Provider]s.
|
||||
// Step 3. Register all cryptography [Provider]s.
|
||||
// Required to install our [SecureRandom] before e.g., UUID asks for one.
|
||||
// This needs to go after initLogging(netty clashes with our logging).
|
||||
// This needs to go after initLogging(netty clashes with our logging)
|
||||
Crypto.registerProviders()
|
||||
|
||||
// Step 5. Print banner and basic node info.
|
||||
// Step 4. Print banner and basic node info.
|
||||
val versionInfo = getVersionInfo()
|
||||
drawBanner(versionInfo)
|
||||
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
|
||||
|
||||
// Step 6. Load and validate node configuration.
|
||||
val configuration = (attempt { loadConfiguration() }.doOnException(handleConfigurationLoadingError(cmdLineOptions.configFile)) as? Try.Success)?.let(Try.Success<NodeConfiguration>::value) ?: return ExitCodes.FAILURE
|
||||
// Step 5. Load and validate node configuration.
|
||||
val configuration = (attempt { cmdLineOptions.loadConfig() }.doOnException(handleConfigurationLoadingError(cmdLineOptions.configFile)) as? Try.Success)?.let(Try.Success<NodeConfiguration>::value)
|
||||
?: return ExitCodes.FAILURE
|
||||
val errors = configuration.validate()
|
||||
if (errors.isNotEmpty()) {
|
||||
logger.error("Invalid node configuration. Errors were:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}")
|
||||
return ExitCodes.FAILURE
|
||||
}
|
||||
|
||||
// Step 7. Configuring special serialisation requirements, i.e., bft-smart relies on Java serialization.
|
||||
attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } as? Try.Success ?: return ExitCodes.FAILURE
|
||||
// Step 6. Configuring special serialisation requirements, i.e., bft-smart relies on Java serialization.
|
||||
attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } as? Try.Success
|
||||
?: return ExitCodes.FAILURE
|
||||
|
||||
// Step 8. Any actions required before starting up the Corda network layer.
|
||||
attempt { preNetworkRegistration(configuration) }.doOnException(handleRegistrationError) as? Try.Success ?: return ExitCodes.FAILURE
|
||||
// Step 7. Any actions required before starting up the Corda network layer.
|
||||
attempt { preNetworkRegistration(configuration) }.doOnException(::handleRegistrationError) as? Try.Success
|
||||
?: return ExitCodes.FAILURE
|
||||
|
||||
// Step 9. Check if in registration mode.
|
||||
checkAndRunRegistrationMode(configuration, versionInfo)?.let {
|
||||
return if (it) ExitCodes.SUCCESS
|
||||
else ExitCodes.FAILURE
|
||||
}
|
||||
|
||||
// Step 10. Log startup info.
|
||||
// Step 8. Log startup info.
|
||||
logStartupInfo(versionInfo, configuration)
|
||||
|
||||
// Step 11. Start node: create the node, check for other command-line options, add extra logging etc.
|
||||
attempt { startNode(configuration, versionInfo, startTime) }.doOnSuccess { logger.info("Node exiting successfully") }.doOnException(handleStartError) as? Try.Success ?: return ExitCodes.FAILURE
|
||||
// Step 9. Start node: create the node, check for other command-line options, add extra logging etc.
|
||||
attempt {
|
||||
cmdLineOptions.baseDirectory.createDirectories()
|
||||
afterNodeInitialisation.run(createNode(configuration, versionInfo))
|
||||
}.doOnException(::handleStartError) as? Try.Success ?: return ExitCodes.FAILURE
|
||||
|
||||
return ExitCodes.SUCCESS
|
||||
}
|
||||
|
||||
private fun checkAndRunRegistrationMode(configuration: NodeConfiguration, versionInfo: VersionInfo): Boolean? {
|
||||
checkUnfinishedRegistration()
|
||||
cmdLineOptions.nodeRegistrationOption?.let {
|
||||
// Null checks for [compatibilityZoneURL], [rootTruststorePath] and [rootTruststorePassword] has been done in [CmdLineOptions.loadConfig]
|
||||
attempt { registerWithNetwork(configuration, versionInfo, it) }.doOnException(handleRegistrationError) as? Try.Success
|
||||
?: return false
|
||||
// At this point the node registration was successful. We can delete the marker file.
|
||||
deleteNodeRegistrationMarker(cmdLineOptions.baseDirectory)
|
||||
return true
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// TODO: Reconsider if automatic re-registration should be applied when something failed during initial registration.
|
||||
// There might be cases where the node user should investigate what went wrong before registering again.
|
||||
private fun checkUnfinishedRegistration() {
|
||||
if (checkRegistrationMode() && !cmdLineOptions.isRegistration) {
|
||||
println("Node was started before with `--initial-registration`, but the registration was not completed.\nResuming registration.")
|
||||
// Pretend that the node was started with `--initial-registration` to help prevent user error.
|
||||
cmdLineOptions.isRegistration = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun <RESULT> attempt(action: () -> RESULT): Try<RESULT> = Try.on(action)
|
||||
|
||||
private fun Exception.isExpectedWhenStartingNode() = startNodeExpectedErrors.any { error -> error.isInstance(this) }
|
||||
|
||||
private val startNodeExpectedErrors = setOf(MultipleCordappsForFlowException::class, CheckpointIncompatibleException::class, AddressBindingException::class, NetworkParametersReader::class, DatabaseIncompatibleException::class)
|
||||
|
||||
private fun Exception.logAsExpected(message: String? = this.message, print: (String?) -> Unit = logger::error) = print(message)
|
||||
|
||||
private fun Exception.logAsUnexpected(message: String? = this.message, error: Exception = this, print: (String?, Throwable) -> Unit = logger::error) = print("$message${this.message?.let { ": $it" } ?: ""}", error)
|
||||
|
||||
private fun Exception.isOpenJdkKnownIssue() = message?.startsWith("Unknown named curve:") == true
|
||||
|
||||
private val handleRegistrationError = { error: Exception ->
|
||||
when (error) {
|
||||
is NodeRegistrationException -> error.logAsExpected("Issue with Node registration: ${error.message}")
|
||||
else -> error.logAsUnexpected("Exception during node registration")
|
||||
}
|
||||
}
|
||||
|
||||
private val handleStartError = { error: Exception ->
|
||||
when {
|
||||
error.isExpectedWhenStartingNode() -> error.logAsExpected()
|
||||
error is CouldNotCreateDataSourceException -> error.logAsUnexpected()
|
||||
error is Errors.NativeIoException && error.message?.contains("Address already in use") == true -> error.logAsExpected("One of the ports required by the Corda node is already in use.")
|
||||
error.isOpenJdkKnownIssue() -> error.logAsExpected("Exception during node startup - ${error.message}. This is a known OpenJDK issue on some Linux distributions, please use OpenJDK from zulu.org or Oracle JDK.")
|
||||
else -> error.logAsUnexpected("Exception during node startup")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleConfigurationLoadingError(configFile: Path) = { error: Exception ->
|
||||
when (error) {
|
||||
is UnknownConfigurationKeysException -> error.logAsExpected()
|
||||
is ConfigException.IO -> error.logAsExpected(configFileNotFoundMessage(configFile), ::println)
|
||||
else -> error.logAsUnexpected("Unexpected error whilst reading node configuration")
|
||||
}
|
||||
}
|
||||
|
||||
private fun configFileNotFoundMessage(configFile: Path): String {
|
||||
return """
|
||||
Unable to load the node config file from '$configFile'.
|
||||
|
||||
Try setting the --base-directory flag to change which directory the node
|
||||
is looking in, or use the --config-file flag to specify it explicitly.
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
private fun loadConfiguration(): NodeConfiguration {
|
||||
val (rawConfig, configurationResult) = loadConfigFile()
|
||||
if (cmdLineOptions.devMode == true) {
|
||||
println("Config:\n${rawConfig.root().render(ConfigRenderOptions.defaults())}")
|
||||
}
|
||||
return configurationResult.getOrThrow()
|
||||
}
|
||||
|
||||
private fun checkRegistrationMode(): Boolean {
|
||||
// If the node was started with `--initial-registration`, create marker file.
|
||||
// We do this here to ensure the marker is created even if parsing the args with NodeArgsParser fails.
|
||||
val marker = cmdLineOptions.baseDirectory / INITIAL_REGISTRATION_MARKER
|
||||
if (!cmdLineOptions.isRegistration && !marker.exists()) {
|
||||
return false
|
||||
}
|
||||
try {
|
||||
marker.createFile()
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Could not create marker file for `--initial-registration`.", e)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun deleteNodeRegistrationMarker(baseDir: Path) {
|
||||
try {
|
||||
val marker = File((baseDir / INITIAL_REGISTRATION_MARKER).toUri())
|
||||
if (marker.exists()) {
|
||||
marker.delete()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.logAsUnexpected("Could not delete the marker file that was created for `--initial-registration`.", print = logger::warn)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun preNetworkRegistration(conf: NodeConfiguration) = Unit
|
||||
|
||||
protected open fun createNode(conf: NodeConfiguration, versionInfo: VersionInfo): Node = Node(conf, versionInfo)
|
||||
open fun createNode(conf: NodeConfiguration, versionInfo: VersionInfo): Node = Node(conf, versionInfo)
|
||||
|
||||
protected open fun startNode(conf: NodeConfiguration, versionInfo: VersionInfo, startTime: Long) {
|
||||
cmdLineOptions.baseDirectory.createDirectories()
|
||||
val node = createNode(conf, versionInfo)
|
||||
if (cmdLineOptions.clearNetworkMapCache) {
|
||||
node.clearNetworkMapCache()
|
||||
return
|
||||
}
|
||||
if (cmdLineOptions.justGenerateNodeInfo) {
|
||||
// Perform the minimum required start-up logic to be able to write a nodeInfo to disk
|
||||
node.generateAndSaveNodeInfo()
|
||||
return
|
||||
}
|
||||
if (cmdLineOptions.justGenerateRpcSslCerts) {
|
||||
generateRpcSslCertificates(conf)
|
||||
return
|
||||
}
|
||||
|
||||
if (conf.devMode) {
|
||||
fun startNode(node: Node, startTime: Long) {
|
||||
if (node.configuration.devMode) {
|
||||
Emoji.renderIfSupported {
|
||||
Node.printWarning("This node is running in developer mode! ${Emoji.developer} This is not safe for production deployment.")
|
||||
Node.printWarning("This node is running in development mode! ${Emoji.developer} This is not safe for production deployment.")
|
||||
}
|
||||
} else {
|
||||
logger.info("The Corda node is running in production mode. If this is a developer environment you can set 'devMode=true' in the node.conf file.")
|
||||
@ -256,7 +191,7 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
|
||||
Node.printBasicNodeInfo("Node for \"$name\" started up and registered in $elapsed sec")
|
||||
|
||||
// Don't start the shell if there's no console attached.
|
||||
if (conf.shouldStartLocalShell()) {
|
||||
if (node.configuration.shouldStartLocalShell()) {
|
||||
node.startupComplete.then {
|
||||
try {
|
||||
InteractiveShell.runLocalShell(node::stop)
|
||||
@ -265,8 +200,8 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (conf.shouldStartSSHDaemon()) {
|
||||
Node.printBasicNodeInfo("SSH server listening on port", conf.sshd!!.port.toString())
|
||||
if (node.configuration.shouldStartSSHDaemon()) {
|
||||
Node.printBasicNodeInfo("SSH server listening on port", node.configuration.sshd!!.port.toString())
|
||||
}
|
||||
},
|
||||
{ th ->
|
||||
@ -275,82 +210,6 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
|
||||
node.run()
|
||||
}
|
||||
|
||||
private fun generateRpcSslCertificates(conf: NodeConfiguration) {
|
||||
val (keyPair, cert) = createKeyPairAndSelfSignedTLSCertificate(conf.myLegalName.x500Principal)
|
||||
|
||||
val keyStorePath = conf.baseDirectory / "certificates" / "rpcsslkeystore.jks"
|
||||
val trustStorePath = conf.baseDirectory / "certificates" / "export" / "rpcssltruststore.jks"
|
||||
|
||||
if (keyStorePath.exists() || trustStorePath.exists()) {
|
||||
println("Found existing RPC SSL keystores. Command was already run. Exiting..")
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
val console: Console? = System.console()
|
||||
|
||||
when (console) {
|
||||
// In this case, the JVM is not connected to the console so we need to exit.
|
||||
null -> {
|
||||
println("Not connected to console. Exiting")
|
||||
exitProcess(1)
|
||||
}
|
||||
// Otherwise we can proceed normally.
|
||||
else -> {
|
||||
while (true) {
|
||||
val keystorePassword1 = console.readPassword("Enter the RPC keystore password => ")
|
||||
// TODO: consider adding a password strength policy.
|
||||
if (keystorePassword1.isEmpty()) {
|
||||
println("The RPC keystore password cannot be an empty String.")
|
||||
continue
|
||||
}
|
||||
|
||||
val keystorePassword2 = console.readPassword("Re-enter the RPC keystore password => ")
|
||||
if (!keystorePassword1.contentEquals(keystorePassword2)) {
|
||||
println("The RPC keystore passwords don't match.")
|
||||
continue
|
||||
}
|
||||
|
||||
saveToKeyStore(keyStorePath, keyPair, cert, String(keystorePassword1), "rpcssl")
|
||||
println("The RPC keystore was saved to: $keyStorePath .")
|
||||
break
|
||||
}
|
||||
|
||||
while (true) {
|
||||
val trustStorePassword1 = console.readPassword("Enter the RPC truststore password => ")
|
||||
// TODO: consider adding a password strength policy.
|
||||
if (trustStorePassword1.isEmpty()) {
|
||||
println("The RPC truststore password cannot be an empty String.")
|
||||
continue
|
||||
}
|
||||
|
||||
val trustStorePassword2 = console.readPassword("Re-enter the RPC truststore password => ")
|
||||
if (!trustStorePassword1.contentEquals(trustStorePassword2)) {
|
||||
println("The RPC truststore passwords don't match.")
|
||||
continue
|
||||
}
|
||||
|
||||
saveToTrustStore(trustStorePath, cert, String(trustStorePassword1), "rpcssl")
|
||||
println("The RPC truststore was saved to: $trustStorePath .")
|
||||
println("You need to distribute this file along with the password in a secure way to all RPC clients.")
|
||||
break
|
||||
}
|
||||
|
||||
val dollar = '$'
|
||||
println("""
|
||||
|
|
||||
|The SSL certificates for RPC were generated successfully.
|
||||
|
|
||||
|Add this snippet to the "rpcSettings" section of your node.conf:
|
||||
| useSsl=true
|
||||
| ssl {
|
||||
| keyStorePath=$dollar{baseDirectory}/certificates/rpcsslkeystore.jks
|
||||
| keyStorePassword=the_above_password
|
||||
| }
|
||||
|""".trimMargin())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun logStartupInfo(versionInfo: VersionInfo, conf: NodeConfiguration) {
|
||||
logger.info("Vendor: ${versionInfo.vendor}")
|
||||
logger.info("Release: ${versionInfo.releaseVersion}")
|
||||
@ -376,40 +235,12 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
|
||||
logger.info(nodeStartedMessage)
|
||||
}
|
||||
|
||||
protected open fun registerWithNetwork(
|
||||
conf: NodeConfiguration,
|
||||
versionInfo: VersionInfo,
|
||||
nodeRegistrationConfig: NodeRegistrationOption
|
||||
) {
|
||||
println("\n" +
|
||||
"******************************************************************\n" +
|
||||
"* *\n" +
|
||||
"* Registering as a new participant with a Corda network *\n" +
|
||||
"* *\n" +
|
||||
"******************************************************************\n")
|
||||
|
||||
NodeRegistrationHelper(conf,
|
||||
HTTPNetworkRegistrationService(
|
||||
requireNotNull(conf.networkServices),
|
||||
versionInfo),
|
||||
nodeRegistrationConfig).buildKeystore()
|
||||
|
||||
// Minimal changes to make registration tool create node identity.
|
||||
// TODO: Move node identity generation logic from node to registration helper.
|
||||
createNode(conf, getVersionInfo()).generateAndSaveNodeInfo()
|
||||
|
||||
println("Successfully registered Corda node with compatibility zone, node identity keys and certificates are stored in '${conf.certificatesDirectory}', it is advised to backup the private keys and certificates.")
|
||||
println("Corda node will now terminate.")
|
||||
}
|
||||
|
||||
protected open fun loadConfigFile(): Pair<Config, Try<NodeConfiguration>> = cmdLineOptions.loadConfig()
|
||||
|
||||
protected open fun banJavaSerialisation(conf: NodeConfiguration) {
|
||||
// Note that in dev mode this filter can be overridden by a notary service implementation.
|
||||
SerialFilter.install(::defaultSerialFilter)
|
||||
}
|
||||
|
||||
protected open fun getVersionInfo(): VersionInfo {
|
||||
open fun getVersionInfo(): VersionInfo {
|
||||
return VersionInfo(
|
||||
PLATFORM_VERSION,
|
||||
CordaVersionProvider.releaseVersion,
|
||||
@ -462,18 +293,6 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
|
||||
}
|
||||
}
|
||||
|
||||
override fun initLogging() {
|
||||
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)
|
||||
Node.renderBasicInfoToConsole = false
|
||||
}
|
||||
System.setProperty("log-path", (cmdLineOptions.baseDirectory / LOGS_DIRECTORY_NAME).toString())
|
||||
SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler.
|
||||
SLF4JBridgeHandler.install()
|
||||
}
|
||||
|
||||
private fun lookupMachineNameAndMaybeWarn(): String {
|
||||
val start = System.currentTimeMillis()
|
||||
val hostName: String = InetAddress.getLocalHost().hostName
|
||||
@ -577,3 +396,66 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
|
||||
}
|
||||
}
|
||||
|
||||
/** Provide some common logging methods for node startup commands. */
|
||||
interface NodeStartupLogging {
|
||||
companion object {
|
||||
val logger by lazy { contextLogger() }
|
||||
val startupErrors = setOf(MultipleCordappsForFlowException::class, CheckpointIncompatibleException::class, AddressBindingException::class, NetworkParametersReader::class, DatabaseIncompatibleException::class)
|
||||
}
|
||||
|
||||
fun <RESULT> attempt(action: () -> RESULT): Try<RESULT> = Try.on(action)
|
||||
|
||||
fun Exception.logAsExpected(message: String? = this.message, print: (String?) -> Unit = logger::error) = print(message)
|
||||
|
||||
fun Exception.logAsUnexpected(message: String? = this.message, error: Exception = this, print: (String?, Throwable) -> Unit = logger::error) = print("$message${this.message?.let { ": $it" } ?: ""}", error)
|
||||
|
||||
fun handleRegistrationError(error: Exception) {
|
||||
when (error) {
|
||||
is NodeRegistrationException -> error.logAsExpected("Issue with Node registration: ${error.message}")
|
||||
else -> error.logAsUnexpected("Exception during node registration")
|
||||
}
|
||||
}
|
||||
|
||||
fun Exception.isOpenJdkKnownIssue() = message?.startsWith("Unknown named curve:") == true
|
||||
|
||||
fun Exception.isExpectedWhenStartingNode() = startupErrors.any { error -> error.isInstance(this) }
|
||||
|
||||
fun handleStartError(error: Exception) {
|
||||
when {
|
||||
error.isExpectedWhenStartingNode() -> error.logAsExpected()
|
||||
error is CouldNotCreateDataSourceException -> error.logAsUnexpected()
|
||||
error is Errors.NativeIoException && error.message?.contains("Address already in use") == true -> error.logAsExpected("One of the ports required by the Corda node is already in use.")
|
||||
error.isOpenJdkKnownIssue() -> error.logAsExpected("Exception during node startup - ${error.message}. This is a known OpenJDK issue on some Linux distributions, please use OpenJDK from zulu.org or Oracle JDK.")
|
||||
else -> error.logAsUnexpected("Exception during node startup")
|
||||
}
|
||||
}
|
||||
|
||||
fun handleConfigurationLoadingError(configFile: Path) = { error: Exception ->
|
||||
when (error) {
|
||||
is UnknownConfigurationKeysException -> error.logAsExpected()
|
||||
is ConfigException.IO -> error.logAsExpected(configFileNotFoundMessage(configFile), ::println)
|
||||
else -> error.logAsUnexpected("Unexpected error whilst reading node configuration")
|
||||
}
|
||||
}
|
||||
|
||||
private fun configFileNotFoundMessage(configFile: Path): String {
|
||||
return """
|
||||
Unable to load the node config file from '$configFile'.
|
||||
|
||||
Try setting the --base-directory flag to change which directory the node
|
||||
is looking in, or use the --config-file flag to specify it explicitly.
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
fun CliWrapperBase.initLogging(baseDirectory: Path) {
|
||||
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)
|
||||
Node.renderBasicInfoToConsole = false
|
||||
}
|
||||
System.setProperty("log-path", (baseDirectory / NodeCliCommand.LOGS_DIRECTORY_NAME).toString())
|
||||
SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler.
|
||||
SLF4JBridgeHandler.install()
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
package net.corda.node.internal.subcommands
|
||||
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.internal.NodeCliCommand
|
||||
import net.corda.node.internal.NodeStartup
|
||||
import net.corda.node.internal.RunAfterNodeInitialisation
|
||||
|
||||
class ClearNetworkCacheCli(startup: NodeStartup): NodeCliCommand("clear-network-cache", "Clears local copy of network map, on node startup it will be restored from server or file system.", startup) {
|
||||
override fun runProgram(): Int {
|
||||
return startup.initialiseAndRun(cmdLineOptions, object: RunAfterNodeInitialisation {
|
||||
override fun run(node: Node) = node.clearNetworkMapCache()
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package net.corda.node.internal.subcommands
|
||||
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.internal.NodeCliCommand
|
||||
import net.corda.node.internal.NodeStartup
|
||||
import net.corda.node.internal.RunAfterNodeInitialisation
|
||||
|
||||
class GenerateNodeInfoCli(startup: NodeStartup): NodeCliCommand("generate-node-info", "Performs the node start-up tasks necessary to generate the nodeInfo file, saves it to disk, then exits.", startup) {
|
||||
override fun runProgram(): Int {
|
||||
return startup.initialiseAndRun(cmdLineOptions, object : RunAfterNodeInitialisation {
|
||||
override fun run(node: Node) {
|
||||
node.generateAndSaveNodeInfo()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package net.corda.node.internal.subcommands
|
||||
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.internal.NodeCliCommand
|
||||
import net.corda.node.internal.NodeStartup
|
||||
import net.corda.node.internal.RunAfterNodeInitialisation
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate
|
||||
import net.corda.node.utilities.saveToKeyStore
|
||||
import net.corda.node.utilities.saveToTrustStore
|
||||
import java.io.Console
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class GenerateRpcSslCertsCli(startup: NodeStartup): NodeCliCommand("generate-rpc-ssl-settings", "Generates the SSL key and trust stores for a secure RPC connection.", startup) {
|
||||
override fun runProgram(): Int {
|
||||
return startup.initialiseAndRun(cmdLineOptions, GenerateRpcSslCerts())
|
||||
}
|
||||
}
|
||||
|
||||
class GenerateRpcSslCerts: RunAfterNodeInitialisation {
|
||||
override fun run(node: Node) {
|
||||
generateRpcSslCertificates(node.configuration)
|
||||
}
|
||||
|
||||
private fun generateRpcSslCertificates(conf: NodeConfiguration) {
|
||||
val (keyPair, cert) = createKeyPairAndSelfSignedTLSCertificate(conf.myLegalName.x500Principal)
|
||||
|
||||
val keyStorePath = conf.baseDirectory / "certificates" / "rpcsslkeystore.jks"
|
||||
val trustStorePath = conf.baseDirectory / "certificates" / "export" / "rpcssltruststore.jks"
|
||||
|
||||
if (keyStorePath.exists() || trustStorePath.exists()) {
|
||||
println("Found existing RPC SSL keystores. Command was already run. Exiting.")
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
val console: Console? = System.console()
|
||||
|
||||
when (console) {
|
||||
// In this case, the JVM is not connected to the console so we need to exit.
|
||||
null -> {
|
||||
println("Not connected to console. Exiting.")
|
||||
exitProcess(1)
|
||||
}
|
||||
// Otherwise we can proceed normally.
|
||||
else -> {
|
||||
while (true) {
|
||||
val keystorePassword1 = console.readPassword("Enter the RPC keystore password:")
|
||||
// TODO: consider adding a password strength policy.
|
||||
if (keystorePassword1.isEmpty()) {
|
||||
println("The RPC keystore password cannot be an empty String.")
|
||||
continue
|
||||
}
|
||||
|
||||
val keystorePassword2 = console.readPassword("Re-enter the RPC keystore password:")
|
||||
if (!keystorePassword1.contentEquals(keystorePassword2)) {
|
||||
println("The RPC keystore passwords don't match.")
|
||||
continue
|
||||
}
|
||||
|
||||
saveToKeyStore(keyStorePath, keyPair, cert, String(keystorePassword1), "rpcssl")
|
||||
println("The RPC keystore was saved to: $keyStorePath .")
|
||||
break
|
||||
}
|
||||
|
||||
while (true) {
|
||||
val trustStorePassword1 = console.readPassword("Enter the RPC truststore password:")
|
||||
// TODO: consider adding a password strength policy.
|
||||
if (trustStorePassword1.isEmpty()) {
|
||||
println("The RPC truststore password cannot be an empty string.")
|
||||
continue
|
||||
}
|
||||
|
||||
val trustStorePassword2 = console.readPassword("Re-enter the RPC truststore password:")
|
||||
if (!trustStorePassword1.contentEquals(trustStorePassword2)) {
|
||||
println("The RPC truststore passwords don't match.")
|
||||
continue
|
||||
}
|
||||
|
||||
saveToTrustStore(trustStorePath, cert, String(trustStorePassword1), "rpcssl")
|
||||
println("The RPC truststore was saved to: $trustStorePath.")
|
||||
println("You need to distribute this file along with the password in a secure way to all RPC clients.")
|
||||
break
|
||||
}
|
||||
|
||||
val dollar = '$'
|
||||
println("""
|
||||
|
|
||||
|The SSL certificates for RPC were generated successfully.
|
||||
|
|
||||
|Add this snippet to the "rpcSettings" section of your node.conf:
|
||||
| useSsl=true
|
||||
| ssl {
|
||||
| keyStorePath=$dollar{baseDirectory}/certificates/rpcsslkeystore.jks
|
||||
| keyStorePassword=the_above_password
|
||||
| }
|
||||
|""".trimMargin())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package net.corda.node.internal.subcommands
|
||||
|
||||
import net.corda.cliutils.CliWrapperBase
|
||||
import net.corda.core.internal.createFile
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.utilities.Try
|
||||
import net.corda.node.InitialRegistrationCmdLineOptions
|
||||
import net.corda.node.NodeRegistrationOption
|
||||
import net.corda.node.internal.*
|
||||
import net.corda.node.internal.NodeStartupLogging.Companion.logger
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
|
||||
import net.corda.node.utilities.registration.NodeRegistrationHelper
|
||||
import picocli.CommandLine.Mixin
|
||||
import picocli.CommandLine.Option
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
|
||||
class InitialRegistrationCli(val startup: NodeStartup): CliWrapperBase("initial-registration", "Starts initial node registration with Corda network to obtain certificate from the permissioning server.") {
|
||||
@Option(names = ["-t", "--network-root-truststore"], description = ["Network root trust store obtained from network operator."])
|
||||
var networkRootTrustStorePathParameter: Path? = null
|
||||
|
||||
@Option(names = ["-p", "--network-root-truststore-password"], description = ["Network root trust store password obtained from network operator."], required = true)
|
||||
var networkRootTrustStorePassword: String = ""
|
||||
|
||||
override fun runProgram() : Int {
|
||||
val networkRootTrustStorePath: Path = networkRootTrustStorePathParameter ?: cmdLineOptions.baseDirectory / "certificates" / "network-root-truststore.jks"
|
||||
return startup.initialiseAndRun(cmdLineOptions, InitialRegistration(cmdLineOptions.baseDirectory, networkRootTrustStorePath, networkRootTrustStorePassword, startup))
|
||||
}
|
||||
|
||||
override fun initLogging() = this.initLogging(cmdLineOptions.baseDirectory)
|
||||
|
||||
@Mixin
|
||||
val cmdLineOptions = InitialRegistrationCmdLineOptions()
|
||||
}
|
||||
|
||||
class InitialRegistration(val baseDirectory: Path, private val networkRootTrustStorePath: Path, networkRootTrustStorePassword: String, private val startup: NodeStartup) : RunAfterNodeInitialisation, NodeStartupLogging {
|
||||
companion object {
|
||||
private const val INITIAL_REGISTRATION_MARKER = ".initialregistration"
|
||||
|
||||
fun checkRegistrationMode(baseDirectory: Path): Boolean {
|
||||
// If the node was started with `--initial-registration`, create marker file.
|
||||
// We do this here to ensure the marker is created even if parsing the args with NodeArgsParser fails.
|
||||
val marker = baseDirectory / INITIAL_REGISTRATION_MARKER
|
||||
if (!marker.exists()) {
|
||||
return false
|
||||
}
|
||||
try {
|
||||
marker.createFile()
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Could not create marker file for `initial-registration`.", e)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private val nodeRegistration = NodeRegistrationOption(networkRootTrustStorePath, networkRootTrustStorePassword)
|
||||
|
||||
private fun registerWithNetwork(conf: NodeConfiguration) {
|
||||
val versionInfo = startup.getVersionInfo()
|
||||
|
||||
println("\n" +
|
||||
"******************************************************************\n" +
|
||||
"* *\n" +
|
||||
"* Registering as a new participant with a Corda network *\n" +
|
||||
"* *\n" +
|
||||
"******************************************************************\n")
|
||||
|
||||
NodeRegistrationHelper(conf,
|
||||
HTTPNetworkRegistrationService(
|
||||
requireNotNull(conf.networkServices),
|
||||
versionInfo),
|
||||
nodeRegistration).buildKeystore()
|
||||
|
||||
// Minimal changes to make registration tool create node identity.
|
||||
// TODO: Move node identity generation logic from node to registration helper.
|
||||
startup.createNode(conf, versionInfo).generateAndSaveNodeInfo()
|
||||
|
||||
println("Successfully registered Corda node with compatibility zone, node identity keys and certificates are stored in '${conf.certificatesDirectory}', it is advised to backup the private keys and certificates.")
|
||||
println("Corda node will now terminate.")
|
||||
}
|
||||
|
||||
private fun initialRegistration(config: NodeConfiguration) {
|
||||
// Null checks for [compatibilityZoneURL], [rootTruststorePath] and [rootTruststorePassword] has been done in [CmdLineOptions.loadConfig]
|
||||
attempt { registerWithNetwork(config) }.doOnException(this::handleRegistrationError) as? Try.Success
|
||||
// At this point the node registration was successful. We can delete the marker file.
|
||||
deleteNodeRegistrationMarker(baseDirectory)
|
||||
}
|
||||
|
||||
private fun deleteNodeRegistrationMarker(baseDir: Path) {
|
||||
try {
|
||||
val marker = File((baseDir / INITIAL_REGISTRATION_MARKER).toUri())
|
||||
if (marker.exists()) {
|
||||
marker.delete()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.logAsUnexpected( "Could not delete the marker file that was created for `initial-registration`.", print = logger::warn)
|
||||
}
|
||||
}
|
||||
|
||||
override fun run(node: Node) {
|
||||
require(networkRootTrustStorePath.exists()) { "Network root trust store path: '$networkRootTrustStorePath' doesn't exist" }
|
||||
if (checkRegistrationMode(baseDirectory)) {
|
||||
println("Node was started before with `--initial-registration`, but the registration was not completed.\nResuming registration.")
|
||||
}
|
||||
initialRegistration(node.configuration)
|
||||
}
|
||||
}
|
||||
|
@ -26,11 +26,6 @@
|
||||
required: false
|
||||
multiParam: false
|
||||
acceptableValues: []
|
||||
- parameterName: "--install-shell-extensions"
|
||||
parameterType: "boolean"
|
||||
required: false
|
||||
multiParam: false
|
||||
acceptableValues: []
|
||||
- parameterName: "--just-generate-node-info"
|
||||
parameterType: "boolean"
|
||||
required: false
|
||||
@ -99,11 +94,6 @@
|
||||
required: false
|
||||
multiParam: true
|
||||
acceptableValues: []
|
||||
- parameterName: "-c"
|
||||
parameterType: "boolean"
|
||||
required: false
|
||||
multiParam: false
|
||||
acceptableValues: []
|
||||
- parameterName: "-d"
|
||||
parameterType: "java.lang.Boolean"
|
||||
required: false
|
@ -2,4 +2,4 @@ package net.corda.node.internal
|
||||
|
||||
import net.corda.testing.CliBackwardsCompatibleTest
|
||||
|
||||
class NodeStartupCompatibilityTest : CliBackwardsCompatibleTest(NodeStartup::class.java)
|
||||
class NodeStartupCompatibilityTest : CliBackwardsCompatibleTest(NodeStartupCli::class.java)
|
@ -1,6 +1,8 @@
|
||||
package net.corda.node.internal
|
||||
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.node.InitialRegistrationCmdLineOptions
|
||||
import net.corda.node.internal.subcommands.InitialRegistrationCli
|
||||
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.BeforeClass
|
||||
@ -11,7 +13,7 @@ import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
class NodeStartupTest {
|
||||
private val startup = NodeStartup()
|
||||
private val startup = NodeStartupCli()
|
||||
|
||||
companion object {
|
||||
private lateinit var workingDirectory: Path
|
||||
@ -30,7 +32,6 @@ class NodeStartupTest {
|
||||
assertThat(startup.cmdLineOptions.configFile).isEqualTo(workingDirectory / "node.conf")
|
||||
assertThat(startup.verbose).isEqualTo(false)
|
||||
assertThat(startup.loggingLevel).isEqualTo(Level.INFO)
|
||||
assertThat(startup.cmdLineOptions.nodeRegistrationOption).isEqualTo(null)
|
||||
assertThat(startup.cmdLineOptions.noLocalShell).isEqualTo(false)
|
||||
assertThat(startup.cmdLineOptions.sshdServer).isEqualTo(false)
|
||||
assertThat(startup.cmdLineOptions.justGenerateNodeInfo).isEqualTo(false)
|
||||
@ -38,7 +39,7 @@ class NodeStartupTest {
|
||||
assertThat(startup.cmdLineOptions.unknownConfigKeysPolicy).isEqualTo(UnknownConfigKeysPolicy.FAIL)
|
||||
assertThat(startup.cmdLineOptions.devMode).isEqualTo(null)
|
||||
assertThat(startup.cmdLineOptions.clearNetworkMapCache).isEqualTo(false)
|
||||
assertThat(startup.cmdLineOptions.networkRootTrustStorePath).isEqualTo(workingDirectory / "certificates" / "network-root-truststore.jks")
|
||||
assertThat(startup.cmdLineOptions.networkRootTrustStorePathParameter).isEqualTo(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -46,6 +47,6 @@ class NodeStartupTest {
|
||||
CommandLine.populateCommand(startup, "--base-directory", (workingDirectory / "another-base-dir").toString())
|
||||
assertThat(startup.cmdLineOptions.baseDirectory).isEqualTo(workingDirectory / "another-base-dir")
|
||||
assertThat(startup.cmdLineOptions.configFile).isEqualTo(workingDirectory / "another-base-dir" / "node.conf")
|
||||
assertThat(startup.cmdLineOptions.networkRootTrustStorePath).isEqualTo(workingDirectory / "another-base-dir" / "certificates" / "network-root-truststore.jks")
|
||||
assertThat(startup.cmdLineOptions.networkRootTrustStorePathParameter).isEqualTo(null)
|
||||
}
|
||||
}
|
||||
|
@ -322,7 +322,7 @@ class DriverDSLImpl(
|
||||
} else {
|
||||
startOutOfProcessMiniNode(
|
||||
config,
|
||||
"--initial-registration",
|
||||
"initial-registration",
|
||||
"--network-root-truststore=${rootTruststorePath.toAbsolutePath()}",
|
||||
"--network-root-truststore-password=$rootTruststorePassword"
|
||||
).map { config }
|
||||
@ -457,7 +457,7 @@ class DriverDSLImpl(
|
||||
} else {
|
||||
// TODO The config we use here is uses a hardocded p2p port which changes when the node is run proper
|
||||
// This causes two node info files to be generated.
|
||||
startOutOfProcessMiniNode(config, "--just-generate-node-info").map {
|
||||
startOutOfProcessMiniNode(config, "generate-node-info").map {
|
||||
// Once done we have to read the signed node info file that's been generated
|
||||
val nodeInfoFile = config.corda.baseDirectory.list { paths ->
|
||||
paths.filter { it.fileName.toString().startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get()
|
||||
|
@ -32,6 +32,7 @@ import rx.internal.schedulers.CachedThreadScheduler
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.test.assertFalse
|
||||
|
||||
// TODO Some of the logic here duplicates what's in the driver - the reason why it's not straightforward to replace it by
|
||||
// using DriverDSLImpl in `init()` and `stopAllNodes()` is because of the platform version passed to nodes (driver doesn't
|
||||
@ -150,9 +151,8 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
|
||||
}
|
||||
|
||||
class InProcessNode(configuration: NodeConfiguration, versionInfo: VersionInfo, flowManager: FlowManager = NodeFlowManager(configuration.flowOverrides)) : Node(configuration, versionInfo, false, flowManager = flowManager) {
|
||||
|
||||
override fun start() : NodeInfo {
|
||||
check(isValidJavaVersion()) { "You are using a version of Java that is not supported (${SystemUtils.JAVA_VERSION}). Please upgrade to the latest version of Java 8." }
|
||||
assertFalse(isInvalidJavaVersion(), "You are using a version of Java that is not supported (${SystemUtils.JAVA_VERSION}). Please upgrade to the latest version of Java 8." )
|
||||
return super.start()
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
|
||||
dependencies {
|
||||
compile group: 'info.picocli', name: 'picocli', version: '3.0.1'
|
||||
compile "info.picocli:picocli:$picocli_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
compile group: "com.fasterxml.jackson.dataformat", name: "jackson-dataformat-yaml", version: "2.9.0"
|
||||
compile group: "com.fasterxml.jackson.core", name: "jackson-databind", version: "2.9.0"
|
||||
|
@ -14,7 +14,6 @@ dependencies {
|
||||
exclude module: 'node-api'
|
||||
exclude module: 'finance'
|
||||
}
|
||||
testCompile project(':test-utils')
|
||||
}
|
||||
|
||||
jar {
|
||||
|
@ -47,7 +47,7 @@ class BlobInspector : CordaCliWrapper("blob-inspector", "Convert AMQP serialised
|
||||
description = ["Display the owningKey and certPath properties of Party and PartyAndReference objects respectively"])
|
||||
private var fullParties: Boolean = false
|
||||
|
||||
@Option(names = ["--schema"], description = ["Print the blob's schema first"])
|
||||
@Option(names = ["--schema"], description = ["Prints the blob's schema first"])
|
||||
private var schema: Boolean = false
|
||||
|
||||
override fun runProgram() = run(System.out)
|
||||
|
@ -0,0 +1,58 @@
|
||||
- commandName: "<main class>"
|
||||
positionalParams:
|
||||
- parameterName: "0"
|
||||
parameterType: "java.net.URL"
|
||||
required: true
|
||||
multiParam: false
|
||||
acceptableValues: []
|
||||
params:
|
||||
- parameterName: "--format"
|
||||
parameterType: "net.corda.blobinspector.OutputFormatType"
|
||||
required: false
|
||||
multiParam: false
|
||||
acceptableValues:
|
||||
- "YAML"
|
||||
- "JSON"
|
||||
- parameterName: "--full-parties"
|
||||
parameterType: "boolean"
|
||||
required: false
|
||||
multiParam: false
|
||||
acceptableValues: []
|
||||
- parameterName: "--input-format"
|
||||
parameterType: "net.corda.blobinspector.InputFormatType"
|
||||
required: false
|
||||
multiParam: false
|
||||
acceptableValues:
|
||||
- "BINARY"
|
||||
- "HEX"
|
||||
- "BASE64"
|
||||
- parameterName: "--log-to-console"
|
||||
parameterType: "boolean"
|
||||
required: false
|
||||
multiParam: false
|
||||
acceptableValues: []
|
||||
- parameterName: "--logging-level"
|
||||
parameterType: "org.slf4j.event.Level"
|
||||
required: false
|
||||
multiParam: false
|
||||
acceptableValues:
|
||||
- "ERROR"
|
||||
- "WARN"
|
||||
- "INFO"
|
||||
- "DEBUG"
|
||||
- "TRACE"
|
||||
- parameterName: "--schema"
|
||||
parameterType: "boolean"
|
||||
required: false
|
||||
multiParam: false
|
||||
acceptableValues: []
|
||||
- parameterName: "--verbose"
|
||||
parameterType: "boolean"
|
||||
required: false
|
||||
multiParam: false
|
||||
acceptableValues: []
|
||||
- parameterName: "-v"
|
||||
parameterType: "boolean"
|
||||
required: false
|
||||
multiParam: false
|
||||
acceptableValues: []
|
@ -25,7 +25,7 @@ class NetworkBootstrapperRunner : CordaCliWrapper("bootstrapper", "Bootstrap a l
|
||||
@Option(names = ["--no-copy"], description = ["""Don't copy the CorDapp JARs into the nodes' "cordapps" directories."""])
|
||||
private var noCopy: Boolean = false
|
||||
|
||||
@Option(names = ["--minimum-platform-version"], description = ["The minimumPlatformVersion to use in the network-parameters"])
|
||||
@Option(names = ["--minimum-platform-version"], description = ["The minimumPlatformVersion to use in the network-parameters."])
|
||||
private var minimumPlatformVersion = PLATFORM_VERSION
|
||||
|
||||
override fun runProgram(): Int {
|
||||
|
@ -6,11 +6,6 @@
|
||||
required: false
|
||||
multiParam: true
|
||||
acceptableValues: []
|
||||
- parameterName: "--install-shell-extensions"
|
||||
parameterType: "boolean"
|
||||
required: false
|
||||
multiParam: false
|
||||
acceptableValues: []
|
||||
- parameterName: "--log-to-console"
|
||||
parameterType: "boolean"
|
||||
required: false
|
||||
|
@ -69,57 +69,43 @@ fun CordaCliWrapper.start(args: Array<String>) {
|
||||
cmd.registerConverter(Path::class.java) { Paths.get(it).toAbsolutePath().normalize() }
|
||||
cmd.commandSpec.name(alias)
|
||||
cmd.commandSpec.usageMessage().description(description)
|
||||
cmd.commandSpec.parser().collectErrors(true)
|
||||
this.subCommands().forEach {
|
||||
val subCommand = CommandLine(it)
|
||||
it.args = args
|
||||
subCommand.commandSpec.usageMessage().description(it.description)
|
||||
cmd.commandSpec.addSubcommand(it.alias, subCommand)
|
||||
}
|
||||
|
||||
try {
|
||||
val defaultAnsiMode = if (CordaSystemUtils.isOsWindows()) {
|
||||
Help.Ansi.ON
|
||||
} else {
|
||||
Help.Ansi.AUTO
|
||||
}
|
||||
|
||||
val results = cmd.parse(*args)
|
||||
val app = cmd.getCommand<CordaCliWrapper>()
|
||||
if (cmd.isUsageHelpRequested) {
|
||||
cmd.usage(System.out, defaultAnsiMode)
|
||||
exitProcess(ExitCodes.SUCCESS)
|
||||
}
|
||||
if (cmd.isVersionHelpRequested) {
|
||||
cmd.printVersionHelp(System.out, defaultAnsiMode)
|
||||
exitProcess(ExitCodes.SUCCESS)
|
||||
}
|
||||
if (app.installShellExtensionsParser.installShellExtensions) {
|
||||
System.out.println("Install shell extensions: ${app.installShellExtensionsParser.installShellExtensions}")
|
||||
// ignore any parsing errors and run the program
|
||||
exitProcess(app.call())
|
||||
}
|
||||
val allErrors = results.flatMap { it.parseResult?.errors() ?: emptyList() }
|
||||
if (allErrors.any()) {
|
||||
val parameterExceptions = allErrors.asSequence().filter { it is ParameterException }
|
||||
if (parameterExceptions.any()) {
|
||||
System.err.println("${ShellConstants.RED}${parameterExceptions.map{ it.message }.joinToString()}${ShellConstants.RESET}")
|
||||
parameterExceptions.filter { it is UnmatchedArgumentException}.forEach { (it as UnmatchedArgumentException).printSuggestions(System.out) }
|
||||
usage(cmd, System.out, defaultAnsiMode)
|
||||
val results = cmd.parseWithHandlers(RunLast().useOut(System.out).useAnsi(defaultAnsiMode),
|
||||
DefaultExceptionHandler<List<Any>>().useErr(System.err).useAnsi(defaultAnsiMode),
|
||||
*args)
|
||||
// If an error code has been returned, use this and exit
|
||||
results?.firstOrNull()?.let {
|
||||
if (it is Int) {
|
||||
exitProcess(it)
|
||||
} else {
|
||||
exitProcess(ExitCodes.FAILURE)
|
||||
}
|
||||
throw allErrors.first()
|
||||
}
|
||||
exitProcess(app.call())
|
||||
} catch (e: Exception) {
|
||||
// If no results returned, picocli ran something without invoking the main program, e.g. --help or --version, so exit successfully
|
||||
exitProcess(ExitCodes.SUCCESS)
|
||||
} catch (e: ExecutionException) {
|
||||
val throwable = e.cause ?: e
|
||||
if (this.verbose) {
|
||||
throwable.printStackTrace()
|
||||
} else {
|
||||
System.err.println("${ShellConstants.RED}${throwable.rootMessage ?: "Use --verbose for more details"}${ShellConstants.RESET}")
|
||||
System.err.println("*ERROR*: ${throwable.rootMessage ?: "Use --verbose for more details"}")
|
||||
}
|
||||
exitProcess(ExitCodes.FAILURE)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple base class for handling help, version, verbose and logging-level commands.
|
||||
* As versionProvider information from the MANIFEST file is used. It can be overwritten by custom version providers (see: Node)
|
||||
* Picocli will prioritise versionProvider from the `@Command` annotation on the subclass, see: https://picocli.info/#_reuse_combinations
|
||||
*/
|
||||
@Command(mixinStandardHelpOptions = true,
|
||||
versionProvider = CordaVersionProvider::class,
|
||||
sortOptions = false,
|
||||
@ -129,9 +115,9 @@ fun CordaCliWrapper.start(args: Array<String>) {
|
||||
parameterListHeading = "%n@|bold,underline Parameters|@:%n%n",
|
||||
optionListHeading = "%n@|bold,underline Options|@:%n%n",
|
||||
commandListHeading = "%n@|bold,underline Commands|@:%n%n")
|
||||
abstract class CordaCliWrapper(val alias: String, val description: String) : Callable<Int> {
|
||||
abstract class CliWrapperBase(val alias: String, val description: String) : Callable<Int> {
|
||||
companion object {
|
||||
private val logger by lazy { loggerFor<CordaCliWrapper>() }
|
||||
private val logger by lazy { contextLogger() }
|
||||
}
|
||||
|
||||
// Raw args are provided for use in logging - this is a lateinit var rather than a constructor parameter as the class
|
||||
@ -148,9 +134,6 @@ abstract class CordaCliWrapper(val alias: String, val description: String) : Cal
|
||||
)
|
||||
var loggingLevel: Level = Level.INFO
|
||||
|
||||
@Mixin
|
||||
lateinit var installShellExtensionsParser: InstallShellExtensionsParser
|
||||
|
||||
// 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() {
|
||||
@ -169,7 +152,32 @@ abstract class CordaCliWrapper(val alias: String, val description: String) : Cal
|
||||
override fun call(): Int {
|
||||
initLogging()
|
||||
logger.info("Application Args: ${args.joinToString(" ")}")
|
||||
installShellExtensionsParser.installOrUpdateShellExtensions(alias, this.javaClass.name)
|
||||
return runProgram()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple base class for handling help, version, verbose and logging-level commands.
|
||||
* As versionProvider information from the MANIFEST file is used. It can be overwritten by custom version providers (see: Node)
|
||||
* Picocli will prioritise versionProvider from the `@Command` annotation on the subclass, see: https://picocli.info/#_reuse_combinations
|
||||
*/
|
||||
abstract class CordaCliWrapper(alias: String, description: String) : CliWrapperBase(alias, description) {
|
||||
companion object {
|
||||
private val logger by lazy { contextLogger() }
|
||||
}
|
||||
|
||||
private val installShellExtensionsParser = InstallShellExtensionsParser(this)
|
||||
|
||||
protected open fun additionalSubCommands(): Set<CliWrapperBase> = emptySet()
|
||||
|
||||
fun subCommands(): Set<CliWrapperBase> {
|
||||
return additionalSubCommands() + installShellExtensionsParser
|
||||
}
|
||||
|
||||
override fun call(): Int {
|
||||
initLogging()
|
||||
logger.info("Application Args: ${args.joinToString(" ")}")
|
||||
installShellExtensionsParser.updateShellExtensions()
|
||||
return runProgram()
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,13 @@ package net.corda.cliutils
|
||||
|
||||
import net.corda.core.internal.*
|
||||
import picocli.CommandLine
|
||||
import picocli.CommandLine.Command
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.util.*
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
private class ShellExtensionsGenerator(val alias: String, val className: String) {
|
||||
private class ShellExtensionsGenerator(val parent: CordaCliWrapper) {
|
||||
private class SettingsFile(val filePath: Path) {
|
||||
private val lines: MutableList<String> by lazy { getFileLines() }
|
||||
var fileModified: Boolean = false
|
||||
@ -68,25 +68,27 @@ private class ShellExtensionsGenerator(val alias: String, val className: String)
|
||||
private fun jarVersion(alias: String) = "# $alias - Version: ${CordaVersionProvider.releaseVersion}, Revision: ${CordaVersionProvider.revision}"
|
||||
private fun getAutoCompleteFileLocation(alias: String) = userHome / ".completion" / alias
|
||||
|
||||
private fun generateAutoCompleteFile(alias: String, className: String) {
|
||||
private fun generateAutoCompleteFile(alias: String) {
|
||||
println("Generating $alias auto completion file")
|
||||
val autoCompleteFile = getAutoCompleteFileLocation(alias)
|
||||
autoCompleteFile.parent.createDirectories()
|
||||
picocli.AutoComplete.main("-f", "-n", alias, className, "-o", autoCompleteFile.toStringWithDeWindowsfication())
|
||||
val hierarchy = CommandLine(parent)
|
||||
parent.subCommands().forEach { hierarchy.addSubcommand(it.alias, it)}
|
||||
|
||||
// Append hash of file to autocomplete file
|
||||
autoCompleteFile.toFile().appendText(jarVersion(alias))
|
||||
val builder = StringBuilder(picocli.AutoComplete.bash(alias, hierarchy))
|
||||
builder.append(jarVersion(alias))
|
||||
autoCompleteFile.writeText(builder.toString())
|
||||
}
|
||||
|
||||
fun installShellExtensions() {
|
||||
// Get jar location and generate alias command
|
||||
val command = "alias $alias='java -jar \"${jarLocation.toStringWithDeWindowsfication()}\"'"
|
||||
generateAutoCompleteFile(alias, className)
|
||||
val command = "alias ${parent.alias}='java -jar \"${jarLocation.toStringWithDeWindowsfication()}\"'"
|
||||
generateAutoCompleteFile(parent.alias)
|
||||
|
||||
// Get bash settings file
|
||||
val bashSettingsFile = SettingsFile(userHome / ".bashrc")
|
||||
// Replace any existing alias. There can be only one.
|
||||
bashSettingsFile.addOrReplaceIfStartsWith("alias $alias", command)
|
||||
bashSettingsFile.addOrReplaceIfStartsWith("alias ${parent.alias}", command)
|
||||
val completionFileCommand = "for bcfile in ~/.completion/* ; do . \$bcfile; done"
|
||||
bashSettingsFile.addIfNotExists(completionFileCommand)
|
||||
bashSettingsFile.updateAndBackupIfNecessary()
|
||||
@ -95,17 +97,17 @@ private class ShellExtensionsGenerator(val alias: String, val className: String)
|
||||
val zshSettingsFile = SettingsFile(userHome / ".zshrc")
|
||||
zshSettingsFile.addIfNotExists("autoload -U +X compinit && compinit")
|
||||
zshSettingsFile.addIfNotExists("autoload -U +X bashcompinit && bashcompinit")
|
||||
zshSettingsFile.addOrReplaceIfStartsWith("alias $alias", command)
|
||||
zshSettingsFile.addOrReplaceIfStartsWith("alias ${parent.alias}", command)
|
||||
zshSettingsFile.addIfNotExists(completionFileCommand)
|
||||
zshSettingsFile.updateAndBackupIfNecessary()
|
||||
|
||||
println("Installation complete, $alias is available in bash with autocompletion. ")
|
||||
println("Type `$alias <options>` from the commandline.")
|
||||
println("Installation complete, ${parent.alias} is available in bash with autocompletion. ")
|
||||
println("Type `${parent.alias} <options>` from the commandline.")
|
||||
println("Restart bash for this to take effect, or run `. ~/.bashrc` in bash or `. ~/.zshrc` in zsh to re-initialise your shell now")
|
||||
}
|
||||
|
||||
fun checkForAutoCompleteUpdate() {
|
||||
val autoCompleteFile = getAutoCompleteFileLocation(alias)
|
||||
val autoCompleteFile = getAutoCompleteFileLocation(parent.alias)
|
||||
|
||||
// If no autocomplete file, it hasn't been installed, so don't do anything
|
||||
if (!autoCompleteFile.exists()) return
|
||||
@ -113,25 +115,21 @@ private class ShellExtensionsGenerator(val alias: String, val className: String)
|
||||
var lastLine = ""
|
||||
autoCompleteFile.toFile().forEachLine { lastLine = it }
|
||||
|
||||
if (lastLine != jarVersion(alias)) {
|
||||
if (lastLine != jarVersion(parent.alias)) {
|
||||
println("Old auto completion file detected... regenerating")
|
||||
generateAutoCompleteFile(alias, className)
|
||||
generateAutoCompleteFile(parent.alias)
|
||||
println("Restart bash for this to take effect, or run `. ~/.bashrc` to re-initialise bash now")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InstallShellExtensionsParser {
|
||||
@CommandLine.Option(names = ["--install-shell-extensions"], description = ["Install alias and autocompletion for bash and zsh"])
|
||||
var installShellExtensions: Boolean = false
|
||||
|
||||
fun installOrUpdateShellExtensions(alias: String, className: String) {
|
||||
val generator = ShellExtensionsGenerator(alias, className)
|
||||
if (installShellExtensions) {
|
||||
generator.installShellExtensions()
|
||||
exitProcess(0)
|
||||
} else {
|
||||
generator.checkForAutoCompleteUpdate()
|
||||
}
|
||||
@Command(helpCommand = true)
|
||||
class InstallShellExtensionsParser(private val cliWrapper: CordaCliWrapper) : CliWrapperBase("install-shell-extensions", "Install alias and autocompletion for bash and zsh") {
|
||||
private val generator = ShellExtensionsGenerator(cliWrapper)
|
||||
override fun runProgram(): Int {
|
||||
generator.installShellExtensions()
|
||||
return ExitCodes.SUCCESS
|
||||
}
|
||||
|
||||
fun updateShellExtensions() = generator.checkForAutoCompleteUpdate()
|
||||
}
|
@ -25,7 +25,7 @@ class NotaryCopier(val cacheDir: File) : NodeCopier(cacheDir) {
|
||||
|
||||
fun generateNodeInfo(dirToGenerateFrom: File): File {
|
||||
val nodeInfoGeneratorProcess = ProcessBuilder()
|
||||
.command(listOf("java", "-jar", "corda.jar", "--just-generate-node-info"))
|
||||
.command(listOf("java", "-jar", "corda.jar", "generate-node-info"))
|
||||
.directory(dirToGenerateFrom)
|
||||
.inheritIO()
|
||||
.start()
|
||||
|
@ -21,11 +21,6 @@
|
||||
required: false
|
||||
multiParam: false
|
||||
acceptableValues: []
|
||||
- parameterName: "--install-shell-extensions"
|
||||
parameterType: "boolean"
|
||||
required: false
|
||||
multiParam: false
|
||||
acceptableValues: []
|
||||
- parameterName: "--log-to-console"
|
||||
parameterType: "boolean"
|
||||
required: false
|
||||
|
Loading…
x
Reference in New Issue
Block a user