mirror of
https://github.com/corda/corda.git
synced 2025-01-01 18:56:44 +00:00
CORDA-3068: Pass base directory when resolving relative paths (#5297)
This commit is contained in:
parent
903cdba57e
commit
e96dcedfc6
@ -49,8 +49,10 @@ interface ConfigParser<T> {
|
|||||||
const val CUSTOM_NODE_PROPERTIES_ROOT = "custom"
|
const val CUSTOM_NODE_PROPERTIES_ROOT = "custom"
|
||||||
|
|
||||||
// TODO Move other config parsing to use parseAs and remove this
|
// TODO Move other config parsing to use parseAs and remove this
|
||||||
|
// This is to enable constructs like:
|
||||||
|
// `val keyStorePassword: String by config`
|
||||||
operator fun <T : Any> Config.getValue(receiver: Any, metadata: KProperty<*>): T {
|
operator fun <T : Any> Config.getValue(receiver: Any, metadata: KProperty<*>): T {
|
||||||
return getValueInternal(metadata.name, metadata.returnType, UnknownConfigKeysPolicy.IGNORE::handle)
|
return getValueInternal(metadata.name, metadata.returnType, UnknownConfigKeysPolicy.IGNORE::handle, nestedPath = null, baseDirectory = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Problems:
|
// Problems:
|
||||||
@ -59,7 +61,8 @@ operator fun <T : Any> Config.getValue(receiver: Any, metadata: KProperty<*>): T
|
|||||||
// - Cannot support a many-to-one relationship between configuration file structures and configuration domain type. This is essential for versioning of the configuration files.
|
// - Cannot support a many-to-one relationship between configuration file structures and configuration domain type. This is essential for versioning of the configuration files.
|
||||||
// - It's complicated and based on reflection, meaning problems with it are typically found at runtime.
|
// - It's complicated and based on reflection, meaning problems with it are typically found at runtime.
|
||||||
// - It doesn't support validation errors in a structured way. If something goes wrong, it throws exceptions, which doesn't support good usability practices like displaying all the errors at once.
|
// - It doesn't support validation errors in a structured way. If something goes wrong, it throws exceptions, which doesn't support good usability practices like displaying all the errors at once.
|
||||||
fun <T : Any> Config.parseAs(clazz: KClass<T>, onUnknownKeys: ((Set<String>, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle, nestedPath: String? = null): T {
|
fun <T : Any> Config.parseAs(clazz: KClass<T>, onUnknownKeys: ((Set<String>, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle,
|
||||||
|
nestedPath: String? = null, baseDirectory: Path? = null): T {
|
||||||
// Use custom parser if provided, instead of treating the object as data class.
|
// Use custom parser if provided, instead of treating the object as data class.
|
||||||
clazz.findAnnotation<CustomConfigParser>()?.let { return uncheckedCast(it.parser.createInstance().parse(this)) }
|
clazz.findAnnotation<CustomConfigParser>()?.let { return uncheckedCast(it.parser.createInstance().parse(this)) }
|
||||||
|
|
||||||
@ -85,7 +88,7 @@ fun <T : Any> Config.parseAs(clazz: KClass<T>, onUnknownKeys: ((Set<String>, log
|
|||||||
// Get the matching property for this parameter
|
// Get the matching property for this parameter
|
||||||
val property = clazz.memberProperties.first { it.name == param.name }
|
val property = clazz.memberProperties.first { it.name == param.name }
|
||||||
val path = defaultToOldPath(property)
|
val path = defaultToOldPath(property)
|
||||||
getValueInternal<Any>(path, param.type, onUnknownKeys, nestedPath)
|
getValueInternal<Any>(path, param.type, onUnknownKeys, nestedPath, baseDirectory)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return constructor.callBy(args)
|
return constructor.callBy(args)
|
||||||
@ -114,11 +117,11 @@ fun Config.toProperties(): Properties {
|
|||||||
{ it.value.unwrapped().toString() })
|
{ it.value.unwrapped().toString() })
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T : Any> Config.getValueInternal(path: String, type: KType, onUnknownKeys: ((Set<String>, logger: Logger) -> Unit), nestedPath: String? = null): T {
|
private fun <T : Any> Config.getValueInternal(path: String, type: KType, onUnknownKeys: ((Set<String>, logger: Logger) -> Unit), nestedPath: String?, baseDirectory: Path?): T {
|
||||||
return uncheckedCast(if (type.arguments.isEmpty()) getSingleValue(path, type, onUnknownKeys, nestedPath) else getCollectionValue(path, type, onUnknownKeys, nestedPath))
|
return uncheckedCast(if (type.arguments.isEmpty()) getSingleValue(path, type, onUnknownKeys, nestedPath, baseDirectory) else getCollectionValue(path, type, onUnknownKeys, nestedPath, baseDirectory))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set<String>, logger: Logger) -> Unit, nestedPath: String? = null): Any? {
|
private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set<String>, logger: Logger) -> Unit, nestedPath: String?, baseDirectory: Path?): Any? {
|
||||||
if (type.isMarkedNullable && !hasPath(path)) return null
|
if (type.isMarkedNullable && !hasPath(path)) return null
|
||||||
val typeClass = type.jvmErasure
|
val typeClass = type.jvmErasure
|
||||||
return try {
|
return try {
|
||||||
@ -132,7 +135,10 @@ private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set
|
|||||||
Duration::class -> getDuration(path)
|
Duration::class -> getDuration(path)
|
||||||
Instant::class -> Instant.parse(getString(path))
|
Instant::class -> Instant.parse(getString(path))
|
||||||
NetworkHostAndPort::class -> NetworkHostAndPort.parse(getString(path))
|
NetworkHostAndPort::class -> NetworkHostAndPort.parse(getString(path))
|
||||||
Path::class -> Paths.get(getString(path))
|
Path::class -> {
|
||||||
|
val pathAsString = getString(path)
|
||||||
|
resolvePath(pathAsString, baseDirectory)
|
||||||
|
}
|
||||||
URL::class -> URL(getString(path))
|
URL::class -> URL(getString(path))
|
||||||
UUID::class -> UUID.fromString(getString(path))
|
UUID::class -> UUID.fromString(getString(path))
|
||||||
X500Principal::class -> X500Principal(getString(path))
|
X500Principal::class -> X500Principal(getString(path))
|
||||||
@ -147,7 +153,7 @@ private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set
|
|||||||
else -> if (typeClass.java.isEnum) {
|
else -> if (typeClass.java.isEnum) {
|
||||||
parseEnum(typeClass.java, getString(path))
|
parseEnum(typeClass.java, getString(path))
|
||||||
} else {
|
} else {
|
||||||
getConfig(path).parseAs(typeClass, onUnknownKeys, nestedPath?.let { "$it.$path" } ?: path)
|
getConfig(path).parseAs(typeClass, onUnknownKeys, nestedPath?.let { "$it.$path" } ?: path, baseDirectory = baseDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: ConfigException.Missing) {
|
} catch (e: ConfigException.Missing) {
|
||||||
@ -155,6 +161,16 @@ private fun Config.getSingleValue(path: String, type: KType, onUnknownKeys: (Set
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun resolvePath(pathAsString: String, baseDirectory: Path?): Path {
|
||||||
|
val path = Paths.get(pathAsString)
|
||||||
|
return if (baseDirectory != null) {
|
||||||
|
// if baseDirectory been specified try resolving path against it. Note if `pathFromConfig` is an absolute path - this instruction has no effect.
|
||||||
|
baseDirectory.resolve(path)
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun ConfigException.Missing.relative(path: String, nestedPath: String?): ConfigException.Missing {
|
private fun ConfigException.Missing.relative(path: String, nestedPath: String?): ConfigException.Missing {
|
||||||
return when {
|
return when {
|
||||||
nestedPath != null -> throw ConfigException.Missing("$nestedPath.$path", this)
|
nestedPath != null -> throw ConfigException.Missing("$nestedPath.$path", this)
|
||||||
@ -162,7 +178,7 @@ private fun ConfigException.Missing.relative(path: String, nestedPath: String?):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys: (Set<String>, logger: Logger) -> Unit, nestedPath: String? = null): Collection<Any> {
|
private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys: (Set<String>, logger: Logger) -> Unit, nestedPath: String?, baseDirectory: Path?): Collection<Any> {
|
||||||
val typeClass = type.jvmErasure
|
val typeClass = type.jvmErasure
|
||||||
require(typeClass == List::class || typeClass == Set::class) { "$typeClass is not supported" }
|
require(typeClass == List::class || typeClass == Set::class) { "$typeClass is not supported" }
|
||||||
val elementClass = type.arguments[0].type?.jvmErasure ?: throw IllegalArgumentException("Cannot work with star projection: $type")
|
val elementClass = type.arguments[0].type?.jvmErasure ?: throw IllegalArgumentException("Cannot work with star projection: $type")
|
||||||
@ -179,7 +195,7 @@ private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys:
|
|||||||
LocalDate::class -> getStringList(path).map(LocalDate::parse)
|
LocalDate::class -> getStringList(path).map(LocalDate::parse)
|
||||||
Instant::class -> getStringList(path).map(Instant::parse)
|
Instant::class -> getStringList(path).map(Instant::parse)
|
||||||
NetworkHostAndPort::class -> getStringList(path).map(NetworkHostAndPort.Companion::parse)
|
NetworkHostAndPort::class -> getStringList(path).map(NetworkHostAndPort.Companion::parse)
|
||||||
Path::class -> getStringList(path).map { Paths.get(it) }
|
Path::class -> getStringList(path).map { resolvePath(it, baseDirectory) }
|
||||||
URL::class -> getStringList(path).map(::URL)
|
URL::class -> getStringList(path).map(::URL)
|
||||||
X500Principal::class -> getStringList(path).map(::X500Principal)
|
X500Principal::class -> getStringList(path).map(::X500Principal)
|
||||||
UUID::class -> getStringList(path).map { UUID.fromString(it) }
|
UUID::class -> getStringList(path).map { UUID.fromString(it) }
|
||||||
@ -188,7 +204,7 @@ private fun Config.getCollectionValue(path: String, type: KType, onUnknownKeys:
|
|||||||
else -> if (elementClass.java.isEnum) {
|
else -> if (elementClass.java.isEnum) {
|
||||||
getStringList(path).map { parseEnum(elementClass.java, it) }
|
getStringList(path).map { parseEnum(elementClass.java, it) }
|
||||||
} else {
|
} else {
|
||||||
getConfigList(path).map { it.parseAs(elementClass, onUnknownKeys) }
|
getConfigList(path).map { it.parseAs(elementClass, onUnknownKeys, baseDirectory = baseDirectory) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: ConfigException.Missing) {
|
} catch (e: ConfigException.Missing) {
|
||||||
|
@ -69,10 +69,11 @@ internal object V1NodeConfigurationSpec : Configuration.Specification<NodeConfig
|
|||||||
|
|
||||||
val messagingServerExternal = configuration[messagingServerExternal] ?: Defaults.messagingServerExternal(configuration[messagingServerAddress])
|
val messagingServerExternal = configuration[messagingServerExternal] ?: Defaults.messagingServerExternal(configuration[messagingServerAddress])
|
||||||
val database = configuration[database] ?: Defaults.database(configuration[devMode])
|
val database = configuration[database] ?: Defaults.database(configuration[devMode])
|
||||||
val cordappDirectories = configuration[cordappDirectories] ?: Defaults.cordappsDirectories(configuration[baseDirectory])
|
val baseDirectoryPath = configuration[baseDirectory]
|
||||||
|
val cordappDirectories = configuration[cordappDirectories] ?: Defaults.cordappsDirectories(baseDirectoryPath)
|
||||||
val result = try {
|
val result = try {
|
||||||
valid<NodeConfigurationImpl, Configuration.Validation.Error>(NodeConfigurationImpl(
|
valid<NodeConfigurationImpl, Configuration.Validation.Error>(NodeConfigurationImpl(
|
||||||
baseDirectory = configuration[baseDirectory],
|
baseDirectory = baseDirectoryPath,
|
||||||
myLegalName = configuration[myLegalName],
|
myLegalName = configuration[myLegalName],
|
||||||
emailAddress = configuration[emailAddress],
|
emailAddress = configuration[emailAddress],
|
||||||
p2pAddress = configuration[p2pAddress],
|
p2pAddress = configuration[p2pAddress],
|
||||||
@ -116,7 +117,7 @@ internal object V1NodeConfigurationSpec : Configuration.Specification<NodeConfig
|
|||||||
attachmentContentCacheSizeMegaBytes = configuration[attachmentContentCacheSizeMegaBytes],
|
attachmentContentCacheSizeMegaBytes = configuration[attachmentContentCacheSizeMegaBytes],
|
||||||
h2port = configuration[h2port],
|
h2port = configuration[h2port],
|
||||||
jarDirs = configuration[jarDirs],
|
jarDirs = configuration[jarDirs],
|
||||||
cordappDirectories = cordappDirectories,
|
cordappDirectories = cordappDirectories.map { baseDirectoryPath.resolve(it) },
|
||||||
cordappSignerKeyFingerprintBlacklist = configuration[cordappSignerKeyFingerprintBlacklist]
|
cordappSignerKeyFingerprintBlacklist = configuration[cordappSignerKeyFingerprintBlacklist]
|
||||||
))
|
))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -5,6 +5,7 @@ import com.typesafe.config.ConfigFactory
|
|||||||
import com.typesafe.config.ConfigParseOptions
|
import com.typesafe.config.ConfigParseOptions
|
||||||
import com.typesafe.config.ConfigValueFactory
|
import com.typesafe.config.ConfigValueFactory
|
||||||
import net.corda.common.configuration.parsing.internal.Configuration
|
import net.corda.common.configuration.parsing.internal.Configuration
|
||||||
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.internal.toPath
|
import net.corda.core.internal.toPath
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
@ -16,7 +17,9 @@ import org.assertj.core.api.Assertions.assertThat
|
|||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TemporaryFolder
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
@ -25,6 +28,11 @@ import kotlin.test.assertFalse
|
|||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class NodeConfigurationImplTest {
|
class NodeConfigurationImplTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val tempFolder = TemporaryFolder()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `can't have dev mode options if not in dev mode`() {
|
fun `can't have dev mode options if not in dev mode`() {
|
||||||
val debugOptions = DevModeOptions()
|
val debugOptions = DevModeOptions()
|
||||||
@ -192,6 +200,24 @@ class NodeConfigurationImplTest {
|
|||||||
assertThat(rawConfig.parseAsNodeConfiguration().isValid).isTrue()
|
assertThat(rawConfig.parseAsNodeConfiguration().isValid).isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `relative path correctly parsed`() {
|
||||||
|
val rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
|
||||||
|
|
||||||
|
// Override base directory to have predictable experience on diff OSes
|
||||||
|
val finalConfig = configOf(
|
||||||
|
// Add substitution values here
|
||||||
|
"baseDirectory" to tempFolder.root.canonicalPath)
|
||||||
|
.withFallback(rawConfig)
|
||||||
|
.resolve()
|
||||||
|
|
||||||
|
val nodeConfiguration = finalConfig.parseAsNodeConfiguration()
|
||||||
|
assertThat(nodeConfiguration.isValid).isTrue()
|
||||||
|
|
||||||
|
val baseDirPath = tempFolder.root.toPath()
|
||||||
|
assertEquals(listOf(baseDirPath / "./myCorDapps1", baseDirPath / "./myCorDapps2"), nodeConfiguration.value().cordappDirectories)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `missing rpcSettings_adminAddress cause a graceful failure`() {
|
fun `missing rpcSettings_adminAddress cause a graceful failure`() {
|
||||||
var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
|
var rawConfig = ConfigFactory.parseResources("working-config.conf", ConfigParseOptions.defaults().setAllowMissing(false))
|
||||||
|
@ -4,6 +4,7 @@ keyStorePassword = "cordacadevpass"
|
|||||||
trustStorePassword = "trustpass"
|
trustStorePassword = "trustpass"
|
||||||
crlCheckSoftFail = true
|
crlCheckSoftFail = true
|
||||||
baseDirectory = "/opt/corda"
|
baseDirectory = "/opt/corda"
|
||||||
|
cordappDirectories = ["./myCorDapps1", "./myCorDapps2"]
|
||||||
dataSourceProperties = {
|
dataSourceProperties = {
|
||||||
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
|
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
|
||||||
dataSource.url = "jdbc:h2:file:blah"
|
dataSource.url = "jdbc:h2:file:blah"
|
||||||
|
Loading…
Reference in New Issue
Block a user