mirror of
https://github.com/corda/corda.git
synced 2025-06-12 20:28:18 +00:00
[CORDA-1763]: Add node CLI option for validating configuration. (#4121)
This commit is contained in:
committed by
GitHub
parent
5086358834
commit
6022cecca5
@ -2,11 +2,9 @@ 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.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
|
||||
@ -39,20 +37,9 @@ open class SharedNodeCmdLineOptions {
|
||||
)
|
||||
var devMode: Boolean? = null
|
||||
|
||||
open fun loadConfig(): NodeConfiguration {
|
||||
return getRawConfig().parseAsNodeConfiguration(unknownConfigKeysPolicy::handle)
|
||||
}
|
||||
open fun parseConfiguration(configuration: Config): NodeConfiguration = configuration.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
|
||||
}
|
||||
open fun rawConfiguration(): Config = ConfigHelper.loadConfig(baseDirectory, configFile)
|
||||
|
||||
fun copyFrom(other: SharedNodeCmdLineOptions) {
|
||||
baseDirectory = other.baseDirectory
|
||||
@ -63,8 +50,8 @@ open class SharedNodeCmdLineOptions {
|
||||
}
|
||||
|
||||
class InitialRegistrationCmdLineOptions : SharedNodeCmdLineOptions() {
|
||||
override fun loadConfig(): NodeConfiguration {
|
||||
return getRawConfig().parseAsNodeConfiguration(unknownConfigKeysPolicy::handle).also { config ->
|
||||
override fun parseConfiguration(configuration: Config): NodeConfiguration {
|
||||
return super.parseConfiguration(configuration).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."
|
||||
@ -134,15 +121,8 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
|
||||
)
|
||||
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())
|
||||
)
|
||||
return rawConfig.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle).also { config ->
|
||||
override fun parseConfiguration(configuration: Config): NodeConfiguration {
|
||||
return super.parseConfiguration(configuration).also { config ->
|
||||
if (isRegistration) {
|
||||
require(!config.devMode) { "Registration cannot occur in development mode" }
|
||||
require(config.compatibilityZoneURL != null || config.networkServices != null) {
|
||||
@ -151,6 +131,18 @@ open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun rawConfiguration(): Config {
|
||||
val configOverrides = mutableMapOf<String, Any>()
|
||||
configOverrides += "noLocalShell" to noLocalShell
|
||||
if (sshdServer) {
|
||||
configOverrides += "sshd" to mapOf("port" to sshdServerPort.toString())
|
||||
}
|
||||
devMode?.let {
|
||||
configOverrides += "devMode" to it
|
||||
}
|
||||
return ConfigHelper.loadConfig(baseDirectory, configFile, configOverrides = ConfigFactory.parseMap(configOverrides))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.node.internal
|
||||
|
||||
import com.typesafe.config.ConfigException
|
||||
import io.netty.channel.unix.Errors
|
||||
import net.corda.cliutils.*
|
||||
import net.corda.core.crypto.Crypto
|
||||
@ -15,12 +14,12 @@ import net.corda.node.*
|
||||
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.internal.subcommands.ValidateConfigurationCli.Companion.logConfigurationErrors
|
||||
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.registration.NodeRegistrationException
|
||||
import net.corda.nodeapi.internal.addShutdownHook
|
||||
import net.corda.nodeapi.internal.config.UnknownConfigurationKeysException
|
||||
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseIncompatibleException
|
||||
import net.corda.tools.shell.InteractiveShell
|
||||
@ -57,15 +56,18 @@ abstract class NodeCliCommand(alias: String, description: String, val startup: N
|
||||
/** Main corda entry point. */
|
||||
open class NodeStartupCli : CordaCliWrapper("corda", "Runs a Corda Node") {
|
||||
open val startup = NodeStartup()
|
||||
@Mixin
|
||||
val cmdLineOptions = NodeCmdLineOptions()
|
||||
|
||||
private val networkCacheCli by lazy { ClearNetworkCacheCli(startup) }
|
||||
private val justGenerateNodeInfoCli by lazy { GenerateNodeInfoCli(startup) }
|
||||
private val justGenerateRpcSslCertsCli by lazy { GenerateRpcSslCertsCli(startup) }
|
||||
private val initialRegistrationCli by lazy { InitialRegistrationCli(startup) }
|
||||
private val validateConfigurationCli by lazy { ValidateConfigurationCli() }
|
||||
|
||||
override fun initLogging() = this.initLogging(cmdLineOptions.baseDirectory)
|
||||
|
||||
override fun additionalSubCommands() = setOf(networkCacheCli, justGenerateNodeInfoCli, justGenerateRpcSslCertsCli, initialRegistrationCli)
|
||||
override fun additionalSubCommands() = setOf(networkCacheCli, justGenerateNodeInfoCli, justGenerateRpcSslCertsCli, initialRegistrationCli, validateConfigurationCli)
|
||||
|
||||
override fun runProgram(): Int {
|
||||
return when {
|
||||
@ -104,9 +106,6 @@ open class NodeStartupCli : CordaCliWrapper("corda", "Runs a Corda Node") {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Mixin
|
||||
val cmdLineOptions = NodeCmdLineOptions()
|
||||
}
|
||||
|
||||
/** This class provides a common set of functionality for starting a Node from command line arguments. */
|
||||
@ -140,13 +139,7 @@ open class NodeStartup : NodeStartupLogging {
|
||||
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
|
||||
|
||||
// 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
|
||||
}
|
||||
val configuration = cmdLineOptions.nodeConfiguration().doOnErrors { errors -> logConfigurationErrors(errors, cmdLineOptions.configFile) }.optional ?: 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
|
||||
@ -430,23 +423,6 @@ interface NodeStartupLogging {
|
||||
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) {
|
||||
|
@ -5,7 +5,7 @@ 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) {
|
||||
class ClearNetworkCacheCli(startup: NodeStartup): NodeCliCommand("clear-network-cache", "Clear 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()
|
||||
|
@ -5,7 +5,7 @@ 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) {
|
||||
class GenerateNodeInfoCli(startup: NodeStartup): NodeCliCommand("generate-node-info", "Perform the node start-up tasks necessary to generate the nodeInfo file, save it to disk, then exit.", startup) {
|
||||
override fun runProgram(): Int {
|
||||
return startup.initialiseAndRun(cmdLineOptions, object : RunAfterNodeInitialisation {
|
||||
override fun run(node: Node) {
|
||||
|
@ -13,7 +13,7 @@ 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) {
|
||||
class GenerateRpcSslCertsCli(startup: NodeStartup): NodeCliCommand("generate-rpc-ssl-settings", "Generate the SSL key and trust stores for a secure RPC connection.", startup) {
|
||||
override fun runProgram(): Int {
|
||||
return startup.initialiseAndRun(cmdLineOptions, GenerateRpcSslCerts())
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ 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.") {
|
||||
class InitialRegistrationCli(val startup: NodeStartup): CliWrapperBase("initial-registration", "Start 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
|
||||
|
||||
|
@ -0,0 +1,84 @@
|
||||
package net.corda.node.internal.subcommands
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigException
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import net.corda.cliutils.CliWrapperBase
|
||||
import net.corda.cliutils.ExitCodes
|
||||
import net.corda.common.validation.internal.Validated
|
||||
import net.corda.common.validation.internal.Validated.Companion.invalid
|
||||
import net.corda.common.validation.internal.Validated.Companion.valid
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.SharedNodeCmdLineOptions
|
||||
import net.corda.node.internal.initLogging
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import picocli.CommandLine.*
|
||||
import java.nio.file.Path
|
||||
|
||||
internal class ValidateConfigurationCli : CliWrapperBase("validate-configuration", "Validate the configuration without starting the node.") {
|
||||
internal companion object {
|
||||
private val logger = loggerFor<ValidateConfigurationCli>()
|
||||
|
||||
internal fun logConfigurationErrors(errors: Iterable<Exception>, configFile: Path) {
|
||||
errors.forEach { error ->
|
||||
when (error) {
|
||||
is ConfigException.IO -> logger.error(configFileNotFoundMessage(configFile))
|
||||
else -> logger.error("Error while parsing node configuration.", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@Mixin
|
||||
private val cmdLineOptions = SharedNodeCmdLineOptions()
|
||||
|
||||
override fun initLogging() = initLogging(cmdLineOptions.baseDirectory)
|
||||
|
||||
override fun runProgram(): Int {
|
||||
val configuration = cmdLineOptions.nodeConfiguration()
|
||||
if (configuration.isInvalid) {
|
||||
logConfigurationErrors(configuration.errors, cmdLineOptions.configFile)
|
||||
return ExitCodes.FAILURE
|
||||
}
|
||||
return ExitCodes.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
internal fun SharedNodeCmdLineOptions.nodeConfiguration(): Valid<NodeConfiguration> = NodeConfigurationParser.invoke(this)
|
||||
|
||||
private object NodeConfigurationParser : (SharedNodeCmdLineOptions) -> Valid<NodeConfiguration> {
|
||||
private val logger = loggerFor<ValidateConfigurationCli>()
|
||||
|
||||
private val configRenderingOptions = ConfigRenderOptions.defaults().setComments(false).setOriginComments(false).setFormatted(true)
|
||||
|
||||
override fun invoke(cmds: SharedNodeCmdLineOptions): Valid<NodeConfiguration> {
|
||||
return attempt(cmds::rawConfiguration).doIfValid(::log).attemptMap(cmds::parseConfiguration).mapValid(::validate)
|
||||
}
|
||||
|
||||
internal fun log(config: Config) = logger.debug("Actual configuration:\n${config.root().render(configRenderingOptions)}")
|
||||
|
||||
private fun validate(configuration: NodeConfiguration): Valid<NodeConfiguration> {
|
||||
return Validated.withResult(configuration, configuration.validate().asSequence().map { error -> IllegalArgumentException(error) }.toSet())
|
||||
}
|
||||
|
||||
private fun <VALUE, MAPPED> Valid<VALUE>.attemptMap(convert: (VALUE) -> MAPPED): Valid<MAPPED> = mapValid { value -> attempt { convert.invoke(value) } }
|
||||
|
||||
private fun <VALUE> attempt(action: () -> VALUE): Valid<VALUE> {
|
||||
return try {
|
||||
valid(action.invoke())
|
||||
} catch (exception: Exception) {
|
||||
return invalid(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private typealias Valid<TARGET> = Validated<TARGET, Exception>
|
Reference in New Issue
Block a user