aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-01-25 10:24:19 +0100
committerAntoine A <>2024-01-25 10:24:19 +0100
commitba418b5d6718596fd1b07611f1e1764c797f1e12 (patch)
treec87d9b43d450885aeda5922708caf2143ffa0374
parent18d9d91d049217f1de0514d274728198a2801005 (diff)
downloadlibeufin-ba418b5d6718596fd1b07611f1e1764c797f1e12.tar.gz
libeufin-ba418b5d6718596fd1b07611f1e1764c797f1e12.tar.bz2
libeufin-ba418b5d6718596fd1b07611f1e1764c797f1e12.zip
Improve taler config
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/Config.kt6
-rw-r--r--bank/src/test/kotlin/helpers.kt2
-rw-r--r--common/src/main/kotlin/Cli.kt19
-rw-r--r--common/src/main/kotlin/TalerConfig.kt143
-rw-r--r--common/src/test/kotlin/TalerConfigTest.kt6
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt2
-rw-r--r--nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt7
-rw-r--r--nexus/src/test/kotlin/CliTest.kt2
-rw-r--r--nexus/src/test/kotlin/Common.kt4
-rw-r--r--nexus/src/test/kotlin/ConfigLoading.kt24
-rw-r--r--nexus/src/test/kotlin/DatabaseTest.kt16
-rw-r--r--testbench/src/main/kotlin/Main.kt5
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