diff options
author | Antoine A <> | 2024-01-25 10:24:19 +0100 |
---|---|---|
committer | Antoine A <> | 2024-01-25 10:24:19 +0100 |
commit | ba418b5d6718596fd1b07611f1e1764c797f1e12 (patch) | |
tree | c87d9b43d450885aeda5922708caf2143ffa0374 | |
parent | 18d9d91d049217f1de0514d274728198a2801005 (diff) | |
download | libeufin-ba418b5d6718596fd1b07611f1e1764c797f1e12.tar.gz libeufin-ba418b5d6718596fd1b07611f1e1764c797f1e12.tar.bz2 libeufin-ba418b5d6718596fd1b07611f1e1764c797f1e12.zip |
Improve taler config
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/Config.kt | 6 | ||||
-rw-r--r-- | bank/src/test/kotlin/helpers.kt | 2 | ||||
-rw-r--r-- | common/src/main/kotlin/Cli.kt | 19 | ||||
-rw-r--r-- | common/src/main/kotlin/TalerConfig.kt | 143 | ||||
-rw-r--r-- | common/src/test/kotlin/TalerConfigTest.kt | 6 | ||||
-rw-r--r-- | nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt | 2 | ||||
-rw-r--r-- | nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 7 | ||||
-rw-r--r-- | nexus/src/test/kotlin/CliTest.kt | 2 | ||||
-rw-r--r-- | nexus/src/test/kotlin/Common.kt | 4 | ||||
-rw-r--r-- | nexus/src/test/kotlin/ConfigLoading.kt | 24 | ||||
-rw-r--r-- | nexus/src/test/kotlin/DatabaseTest.kt | 16 | ||||
-rw-r--r-- | testbench/src/main/kotlin/Main.kt | 5 |
12 files changed, 96 insertions, 140 deletions
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt index a7ee5c58..0ad5590a 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt @@ -71,11 +71,7 @@ sealed class ServerConfig { data class Tcp(val port: Int): ServerConfig() } -fun talerConfig(configPath: String?): TalerConfig { - val config = TalerConfig(BANK_CONFIG_SOURCE) - config.load(configPath) - return config -} +fun talerConfig(configPath: Path?): TalerConfig = BANK_CONFIG_SOURCE.fromFile(configPath) fun TalerConfig.loadDbConfig(): DatabaseConfig { return DatabaseConfig( diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt index 6e5f6d02..fa8b63a4 100644 --- a/bank/src/test/kotlin/helpers.kt +++ b/bank/src/test/kotlin/helpers.kt @@ -57,7 +57,7 @@ fun setup( conf: String = "test.conf", lambda: suspend (Database, BankConfig) -> Unit ) { - val config = talerConfig("conf/$conf") + val config = talerConfig(Path("conf/$conf")) val dbCfg = config.loadDbConfig() val ctx = config.loadBankConfig() Database(dbCfg.dbConnStr, ctx.regionalCurrency, ctx.fiatCurrency).use { diff --git a/common/src/main/kotlin/Cli.kt b/common/src/main/kotlin/Cli.kt index 2db1e906..0209dc53 100644 --- a/common/src/main/kotlin/Cli.kt +++ b/common/src/main/kotlin/Cli.kt @@ -27,6 +27,7 @@ import com.github.ajalt.clikt.parameters.groups.* import org.slf4j.Logger import org.slf4j.LoggerFactory import org.slf4j.event.Level +import java.nio.file.Path private val logger: Logger = LoggerFactory.getLogger("libeufin-config") @@ -51,21 +52,11 @@ fun cliCmd(logger: Logger, level: Level, lambda: () -> Unit) { } } -private fun talerConfig(configSource: ConfigSource, configPath: String?): TalerConfig { - val config = TalerConfig(configSource) - config.load(configPath) - return config -} - class CommonOption: OptionGroup() { val config by option( "--config", "-c", help = "Specifies the configuration file" - ).path( - mustExist = true, - canBeDir = false, - mustBeReadable = true, - ).convert { it.toString() } // TODO take path to load config + ).path() val log by option( "--log", "-L", help = "Configure logging to use LOGLEVEL" @@ -91,7 +82,7 @@ private class CliConfigGet(private val configSource: ConfigSource) : CliktComman override fun run() = cliCmd(logger, common.log) { - val config = talerConfig(configSource, common.config) + val config = configSource.fromFile(common.config) if (isPath) { val res = config.lookupPath(sectionName, optionName) if (res == null) { @@ -115,7 +106,7 @@ private class CliConfigPathsub(private val configSource: ConfigSource) : CliktCo private val pathExpr by argument() override fun run() = cliCmd(logger, common.log) { - val config = talerConfig(configSource, common.config) + val config = configSource.fromFile(common.config) println(config.pathsub(pathExpr)) } } @@ -124,7 +115,7 @@ private class CliConfigDump(private val configSource: ConfigSource) : CliktComma private val common by CommonOption() override fun run() = cliCmd(logger, common.log) { - val config = talerConfig(configSource, common.config) + val config = configSource.fromFile(common.config) println("# install path: ${config.getInstallPath()}") println(config.stringify()) } diff --git a/common/src/main/kotlin/TalerConfig.kt b/common/src/main/kotlin/TalerConfig.kt index f98f6e5e..26fd4c5f 100644 --- a/common/src/main/kotlin/TalerConfig.kt +++ b/common/src/main/kotlin/TalerConfig.kt @@ -61,6 +61,21 @@ data class ConfigSource( val installPathBinary: String = "taler-config", ) +fun ConfigSource.fromMem(content: String): TalerConfig { + val cfg = TalerConfig(this) + cfg.loadDefaults() + cfg.loadFromMem(content, null) + return cfg +} + +fun ConfigSource.fromFile(file: Path?): TalerConfig { + val cfg = TalerConfig(this) + cfg.loadDefaults() + val path = file ?: cfg.findDefaultConfigFilename() + if (path != null) cfg.loadFromFile(path) + return cfg +} + /** * Reader and writer for Taler-style configuration files. * @@ -69,8 +84,8 @@ data class ConfigSource( * * @param configSource information about where to load configuration defaults from */ -class TalerConfig( - private val configSource: ConfigSource, +class TalerConfig internal constructor( + val configSource: ConfigSource, ) { private val sectionMap: MutableMap<String, Section> = mutableMapOf() @@ -79,7 +94,55 @@ class TalerConfig( private val installPathBinary = configSource.installPathBinary val sections: Set<String> get() = sectionMap.keys - private fun internalLoadFromString(s: String, source: Path?) { + /** + * Load configuration defaults from the file system + * and populate the PATHS section based on the installation path. + */ + internal fun loadDefaults() { + val installDir = getInstallPath() + val baseConfigDir = Path(installDir, "share/$projectName/config.d") + setSystemDefault("PATHS", "PREFIX", "$installDir/") + setSystemDefault("PATHS", "BINDIR", "$installDir/bin/") + setSystemDefault("PATHS", "LIBEXECDIR", "$installDir/$projectName/libexec/") + setSystemDefault("PATHS", "DOCDIR", "$installDir/share/doc/$projectName/") + setSystemDefault("PATHS", "ICONDIR", "$installDir/share/icons/") + setSystemDefault("PATHS", "LOCALEDIR", "$installDir/share/locale/") + setSystemDefault("PATHS", "LIBDIR", "$installDir/lib/$projectName/") + setSystemDefault("PATHS", "DATADIR", "$installDir/share/$projectName/") + for (filePath in baseConfigDir.listDirectoryEntries()) { + loadFromFile(filePath) + } + } + + private fun loadFromGlob(source: Path, glob: String) { + // FIXME: Check that the Kotlin glob matches the glob from our spec + for (entry in source.parent.listDirectoryEntries(glob)) { + loadFromFile(entry) + } + } + + private fun loadSecret(sectionName: String, secretFilename: Path) { + if (!secretFilename.isReadable()) { + logger.warn("unable to read secrets from $secretFilename") + } else { + loadFromFile(secretFilename) + } + } + + internal fun loadFromFile(file: Path) { + val content = try { + file.readText() + } catch (e: Exception) { + when { + e is NoSuchFileException -> throw Exception("Could not read config at '$file': no such file") + e is AccessDeniedException -> throw Exception("Could not read config at '$file': permission denied") + else -> throw Exception("Could not read config at '$file'", e) + } + } + loadFromMem(content, file) + } + + internal fun loadFromMem(s: String, source: Path?) { val lines = s.lines() var lineNum = 0 var currentSection: String? = null @@ -102,11 +165,11 @@ class TalerConfig( when (directiveName) { "inline" -> { val innerFilename = source.resolveSibling(directiveArg.trim()) - this.loadFromFilename(innerFilename) + loadFromFile(innerFilename) } "inline-matching" -> { val glob = directiveArg.trim() - this.loadFromGlob(source, glob) + loadFromGlob(source, glob) } "inline-secret" -> { val arg = directiveArg.trim() @@ -151,21 +214,6 @@ class TalerConfig( } } - private fun loadFromGlob(source: Path, glob: String) { - // FIXME: Check that the Kotlin glob matches the glob from our spec - for (entry in source.parent.listDirectoryEntries(glob)) { - loadFromFilename(entry) - } - } - - private fun loadSecret(sectionName: String, secretFilename: Path) { - if (!secretFilename.isReadable()) { - logger.warn("unable to read secrets from $secretFilename") - } else { - this.loadFromFilename(secretFilename) - } - } - private fun provideSection(name: String): Section { val canonSecName = name.uppercase() val existingSec = this.sectionMap[canonSecName] @@ -177,10 +225,6 @@ class TalerConfig( return newSection } - fun loadFromString(s: String) { - internalLoadFromString(s, null) - } - private fun setSystemDefault(section: String, option: String, value: String) { // FIXME: The value should be marked as a system default for diagnostics pretty printing val sec = provideSection(section) @@ -213,38 +257,6 @@ class TalerConfig( return outStr.toString() } - /** - * Read values into the configuration from the given entry point - * filename. Defaults are *not* loaded automatically. - */ - fun loadFromFilename(path: Path) { - internalLoadFromString(path.readText(), path) - } - - private fun loadDefaultsFromDir(dirname: Path) { - for (filePath in dirname.listDirectoryEntries()) { - loadFromFilename(filePath) - } - } - - /** - * Load configuration defaults from the file system - * and populate the PATHS section based on the installation path. - */ - fun loadDefaults() { - val installDir = getInstallPath() - val baseConfigDir = Path(installDir, "share/$projectName/config.d") - setSystemDefault("PATHS", "PREFIX", "$installDir/") - setSystemDefault("PATHS", "BINDIR", "$installDir/bin/") - setSystemDefault("PATHS", "LIBEXECDIR", "$installDir/$projectName/libexec/") - setSystemDefault("PATHS", "DOCDIR", "$installDir/share/doc/$projectName/") - setSystemDefault("PATHS", "ICONDIR", "$installDir/share/icons/") - setSystemDefault("PATHS", "LOCALEDIR", "$installDir/share/locale/") - setSystemDefault("PATHS", "LIBDIR", "$installDir/lib/$projectName/") - setSystemDefault("PATHS", "DATADIR", "$installDir/share/$projectName/") - loadDefaultsFromDir(baseConfigDir) - } - private fun variableLookup(x: String, recursionDepth: Int = 0): Path? { val pathRes = this.lookupString("PATHS", x) if (pathRes != null) { @@ -346,28 +358,11 @@ class TalerConfig( } /** - * Load configuration values from the file system. - * If no entrypoint is specified, the default entrypoint - * is used. - */ - fun load(entrypoint: String? = null) { - loadDefaults() - if (entrypoint != null) { - loadFromFilename(Path(entrypoint)) - } else { - val defaultFilename = findDefaultConfigFilename() - if (defaultFilename != null) { - loadFromFilename(defaultFilename) - } - } - } - - /** * Determine the filename of the default configuration file. * * If no such file can be found, return null. */ - private fun findDefaultConfigFilename(): Path? { + internal fun findDefaultConfigFilename(): Path? { val xdg = System.getenv("XDG_CONFIG_HOME") val home = System.getenv("HOME") diff --git a/common/src/test/kotlin/TalerConfigTest.kt b/common/src/test/kotlin/TalerConfigTest.kt index 0167aa00..6cc7aa04 100644 --- a/common/src/test/kotlin/TalerConfigTest.kt +++ b/common/src/test/kotlin/TalerConfigTest.kt @@ -27,9 +27,7 @@ class TalerConfigTest { @Test fun parsing() { // We assume that libeufin-bank is installed. We could also try to locate the source tree here. - val conf = TalerConfig(ConfigSource("libeufin", "libeufin-bank", "libeufin-bank")) - conf.loadDefaults() - conf.loadFromString( + val conf = ConfigSource("libeufin", "libeufin-bank", "libeufin-bank").fromMem( """ [foo] @@ -49,7 +47,7 @@ class TalerConfigTest { @Test fun substitution() { // We assume that libeufin-bank is installed. We could also try to locate the source tree here. - val conf = TalerConfig(ConfigSource("libeufin", "libeufin-bank", "libeufin-bank")) + val conf = ConfigSource("libeufin", "libeufin-bank", "libeufin-bank").fromFile(null) conf.putValueString("PATHS", "DATADIR", "mydir") conf.putValueString("foo", "bar", "baz") conf.putValueString("foo", "bar2", "baz") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt index 32655979..f23b3a33 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt @@ -189,7 +189,7 @@ suspend fun doKeysRequestAndUpdateState( * @param configFile location of the configuration entry point. * @return internal representation of the configuration. */ -fun extractEbicsConfig(configFile: String?): EbicsSetupConfig { +fun extractEbicsConfig(configFile: Path?): EbicsSetupConfig { val config = loadConfig(configFile) return EbicsSetupConfig(config) } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt index 4d27910a..a5f8dccf 100644 --- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt +++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -33,6 +33,7 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import tech.libeufin.nexus.ebics.* import tech.libeufin.common.* +import java.nio.file.Path val NEXUS_CONFIG_SOURCE = ConfigSource("libeufin", "libeufin-nexus", "libeufin-nexus") val logger: Logger = LoggerFactory.getLogger("libeufin-nexus") @@ -195,11 +196,7 @@ class EbicsSetupConfig(val config: TalerConfig) { * @param configFile potentially NULL configuration file location. * @return the configuration handle. */ -fun loadConfig(configFile: String?): TalerConfig { - val config = TalerConfig(NEXUS_CONFIG_SOURCE) - config.load(configFile) - return config -} +fun loadConfig(configFile: Path?): TalerConfig = NEXUS_CONFIG_SOURCE.fromFile(configFile) /** * Abstracts fetching the DB config values to set up Nexus. diff --git a/nexus/src/test/kotlin/CliTest.kt b/nexus/src/test/kotlin/CliTest.kt index 56570bea..de41523e 100644 --- a/nexus/src/test/kotlin/CliTest.kt +++ b/nexus/src/test/kotlin/CliTest.kt @@ -49,7 +49,7 @@ class CliTest { val cmds = listOf("ebics-submit", "ebics-fetch") val allCmds = listOf("ebics-submit", "ebics-fetch", "ebics-setup") val conf = "conf/test.conf" - val cfg = loadConfig(conf) + val cfg = loadConfig(Path(conf)) val clientKeysPath = cfg.requirePath("nexus-ebics", "client_private_keys_file") val bankKeysPath = cfg.requirePath("nexus-ebics", "bank_public_keys_file") clientKeysPath.parent!!.createDirectories() diff --git a/nexus/src/test/kotlin/Common.kt b/nexus/src/test/kotlin/Common.kt index f6db6bfe..422450fc 100644 --- a/nexus/src/test/kotlin/Common.kt +++ b/nexus/src/test/kotlin/Common.kt @@ -34,13 +34,11 @@ val j = Json { } val config: EbicsSetupConfig = run { - val handle = TalerConfig(NEXUS_CONFIG_SOURCE) - handle.load() + val handle = NEXUS_CONFIG_SOURCE.fromFile(null) EbicsSetupConfig(handle) } fun prepDb(cfg: TalerConfig): Database { - cfg.loadDefaults() val dbCfg = DatabaseConfig( dbConnStr = "postgresql:///libeufincheck", sqlDir = cfg.requirePath("paths", "datadir").resolve("sql") diff --git a/nexus/src/test/kotlin/ConfigLoading.kt b/nexus/src/test/kotlin/ConfigLoading.kt index 1155763c..a4047dff 100644 --- a/nexus/src/test/kotlin/ConfigLoading.kt +++ b/nexus/src/test/kotlin/ConfigLoading.kt @@ -31,37 +31,17 @@ class ConfigLoading { */ @Test fun loadRequiredValues() { - val handle = TalerConfig(NEXUS_CONFIG_SOURCE) - handle.load() + val handle = NEXUS_CONFIG_SOURCE.fromFile(null) val cfg = EbicsSetupConfig(handle) cfg._dump() } @Test fun loadPath() { - val handle = TalerConfig(NEXUS_CONFIG_SOURCE) - handle.load() + val handle = NEXUS_CONFIG_SOURCE.fromFile(null) val cfg = EbicsSetupConfig(handle) } - - /** - * Tests that if the configuration lacks at least one option, then - * the config loader throws exception. - */ - @Test - fun detectMissingValues() { - val handle = TalerConfig(NEXUS_CONFIG_SOURCE) - handle.loadFromString(""" - [ebics-nexus] - # All the other defaults won't be loaded. - BANK_DIALECT = postfinance - """.trimIndent()) - assertFailsWith<TalerConfigError> { - EbicsSetupConfig(handle) - } - } - // Checks converting human-readable durations to seconds. @Test fun timeParsing() { diff --git a/nexus/src/test/kotlin/DatabaseTest.kt b/nexus/src/test/kotlin/DatabaseTest.kt index 58fa9735..6e2443e7 100644 --- a/nexus/src/test/kotlin/DatabaseTest.kt +++ b/nexus/src/test/kotlin/DatabaseTest.kt @@ -29,7 +29,7 @@ import kotlin.test.assertEquals class OutgoingPaymentsTest { @Test fun register() { - val db = prepDb(TalerConfig(NEXUS_CONFIG_SOURCE)) + val db = prepDb(NEXUS_CONFIG_SOURCE.fromFile(null)) runBlocking { // With reconciling genOutPay("paid by nexus", "first").run { @@ -65,7 +65,7 @@ class IncomingPaymentsTest { // Tests creating and bouncing incoming payments in one DB transaction. @Test fun bounce() { - val db = prepDb(TalerConfig(NEXUS_CONFIG_SOURCE)) + val db = prepDb(NEXUS_CONFIG_SOURCE.fromFile(null)) runBlocking { // creating and bouncing one incoming transaction. val payment = genInPay("incoming and bounced") @@ -122,7 +122,7 @@ class IncomingPaymentsTest { // Tests the creation of a talerable incoming payment. @Test fun talerable() { - val db = prepDb(TalerConfig(NEXUS_CONFIG_SOURCE)) + val db = prepDb(NEXUS_CONFIG_SOURCE.fromFile(null)) val reservePub = ByteArray(32) Random.nextBytes(reservePub) @@ -142,7 +142,7 @@ class PaymentInitiationsTest { // Testing the insertion of the failure message. @Test fun setFailureMessage() { - val db = prepDb(TalerConfig(NEXUS_CONFIG_SOURCE)) + val db = prepDb(NEXUS_CONFIG_SOURCE.fromFile(null)) runBlocking { assertEquals( db.initiatedPaymentCreate(genInitPay("not submitted, has row ID == 1")), @@ -166,7 +166,7 @@ class PaymentInitiationsTest { // Tests the flagging of payments as submitted. @Test fun paymentInitiationSetAsSubmitted() { - val db = prepDb(TalerConfig(NEXUS_CONFIG_SOURCE)) + val db = prepDb(NEXUS_CONFIG_SOURCE.fromFile(null)) val getRowOne = """ SELECT submitted FROM initiated_outgoing_transactions @@ -199,7 +199,7 @@ class PaymentInitiationsTest { // retrieving only one non-submitted payment. @Test fun paymentInitiation() { - val db = prepDb(TalerConfig(NEXUS_CONFIG_SOURCE)) + val db = prepDb(NEXUS_CONFIG_SOURCE.fromFile(null)) runBlocking { val beEmpty = db.initiatedPaymentsSubmittableGet("KUDOS") // expect no records. assertEquals(beEmpty.size, 0) @@ -232,7 +232,7 @@ class PaymentInitiationsTest { */ @Test fun submittablePayments() { - val db = prepDb(TalerConfig(NEXUS_CONFIG_SOURCE)) + val db = prepDb(NEXUS_CONFIG_SOURCE.fromFile(null)) runBlocking { val beEmpty = db.initiatedPaymentsSubmittableGet("KUDOS") assertEquals(0, beEmpty.size) @@ -268,7 +268,7 @@ class PaymentInitiationsTest { // multiple unsubmitted payment initiations. @Test fun paymentInitiationsMultiple() { - val db = prepDb(TalerConfig(NEXUS_CONFIG_SOURCE)) + val db = prepDb(NEXUS_CONFIG_SOURCE.fromFile(null)) runBlocking { assertEquals(db.initiatedPaymentCreate(genInitPay("#1", "unique1")), PaymentInitiationOutcome.SUCCESS) assertEquals(db.initiatedPaymentCreate(genInitPay("#2", "unique2")), PaymentInitiationOutcome.SUCCESS) diff --git a/testbench/src/main/kotlin/Main.kt b/testbench/src/main/kotlin/Main.kt index 204ffc00..076d5001 100644 --- a/testbench/src/main/kotlin/Main.kt +++ b/testbench/src/main/kotlin/Main.kt @@ -81,8 +81,9 @@ class Cli : CliktCommand("Run integration tests on banks provider") { // Augment config val simpleCfg = Path("test/$platform/ebics.conf").readText() - val conf = "test/$platform/ebics.edited.conf" - Path(conf).writeText("""$simpleCfg + val conf = Path("test/$platform/ebics.edited.conf") + conf.writeText( + """$simpleCfg [paths] LIBEUFIN_NEXUS_HOME = test/$platform |