From 9e3fc09bb056948d0706386edf52cd3a27b184ba Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 2 May 2024 17:09:17 +0200 Subject: harness,util: implement writing changed config back to the file system, support multiple projects --- packages/taler-util/src/talerconfig.ts | 151 ++++++++++++++++++++++++++------- 1 file changed, 121 insertions(+), 30 deletions(-) (limited to 'packages/taler-util/src') diff --git a/packages/taler-util/src/talerconfig.ts b/packages/taler-util/src/talerconfig.ts index 83c0044be..2bd7b355f 100644 --- a/packages/taler-util/src/talerconfig.ts +++ b/packages/taler-util/src/talerconfig.ts @@ -23,13 +23,12 @@ /** * Imports */ -import { AmountJson } from "./amounts.js"; -import { Amounts } from "./amounts.js"; +import { AmountJson, Amounts } from "./amounts.js"; import { Logger } from "./logging.js"; -import nodejs_path from "path"; -import nodejs_os from "os"; import nodejs_fs from "fs"; +import nodejs_os from "os"; +import nodejs_path from "path"; const logger = new Logger("talerconfig.ts"); @@ -76,6 +75,54 @@ interface Section { type SectionMap = { [sectionName: string]: Section }; +/** + * Different projects use the GNUnet/Taler-Style config. + * + * The config source determines where to locate the configuration. + */ +export interface ConfigSource { + projectName: string; + componentName: string; + installPathBinary: string; + baseConfigVarname: string; + prefixVarname: string; +} + +export type ConfigSourceDef = { [x: string]: ConfigSource | undefined }; + +export const ConfigSources = { + ["taler"]: { + projectName: "taler", + componentName: "taler", + installPathBinary: "taler-config", + baseConfigVarname: "TALER_BASE_CONFIG", + prefixVarname: "TALER_PREFIX", + } satisfies ConfigSource, + ["libeufin-bank"]: { + projectName: "libeufin", + componentName: "libeufin-bank", + installPathBinary: "libeufin-bank", + baseConfigVarname: "LIBEUFIN_BASE_CONFIG", + prefixVarname: "LIBEUFIN_PREFIX", + } satisfies ConfigSource, + ["libeufin-nexus"]: { + projectName: "libeufin", + componentName: "libeufin-nexus", + installPathBinary: "libeufin-nexus", + baseConfigVarname: "LIBEUFIN_BASE_CONFIG", + prefixVarname: "LIBEUFIN_PREFIX", + } satisfies ConfigSource, + ["gnunet"]: { + projectName: "gnunet", + componentName: "gnunet", + installPathBinary: "gnunet-config", + baseConfigVarname: "GNUNET_BASE_CONFIG", + prefixVarname: "GNUNET_PREFIX", + } satisfies ConfigSource, +} satisfies ConfigSourceDef; + +const defaultConfigSource: ConfigSource = ConfigSources.taler; + export class ConfigValue { constructor( private sectionName: string, @@ -215,7 +262,7 @@ export function pathsub( return s; } -export interface LoadOptions { +interface LoadOptions { filename?: string; banDirectives?: boolean; } @@ -310,6 +357,14 @@ export class Configuration { private nestLevel = 0; + /** + * Does the entrypoint config file contain complex + * directives? + */ + private entrypointIsComplex: boolean = false; + + constructor(private configSource: ConfigSource = defaultConfigSource) {} + private loadFromFilename( filename: string, isDefaultSource: boolean, @@ -434,6 +489,9 @@ export class Configuration { `invalid configuration, directive in ${fn}:${lineNo} forbidden`, ); } + if (!isDefaultSource) { + this.entrypointIsComplex = true; + } const directive = directiveMatch[1].toLowerCase(); switch (directive) { case "inline": { @@ -521,10 +579,6 @@ export class Configuration { } } - loadFromString(s: string, opts: LoadOptions = {}): void { - return this.internalLoadFromString(s, false, opts); - } - private provideSection(section: string): Section { const secNorm = section.toUpperCase(); if (this.sectionMap[secNorm]) { @@ -653,7 +707,7 @@ export class Configuration { ); } - loadDefaultsFromDir(dirname: string): void { + private loadDefaultsFromDir(dirname: string): void { const files = nodejs_fs.readdirSync(dirname); for (const f of files) { const fn = nodejs_path.join(dirname, f); @@ -662,26 +716,28 @@ export class Configuration { } private loadDefaults(): void { - let baseConfigDir = process.env["TALER_BASE_CONFIG"]; + const { projectName, prefixVarname, baseConfigVarname, installPathBinary } = + this.configSource; + let baseConfigDir = process.env[baseConfigVarname]; if (!baseConfigDir) { /* Try to locate the configuration based on the location * of the taler-config binary. */ - const path = which("taler-config"); + const path = which(installPathBinary); if (path) { baseConfigDir = nodejs_fs.realpathSync( - nodejs_path.dirname(path) + "/../share/taler/config.d", + nodejs_path.dirname(path) + `/../share/${projectName}/config.d`, ); } } if (!baseConfigDir) { - baseConfigDir = "/usr/share/taler/config.d"; + baseConfigDir = `/usr/share/${projectName}/config.d`; } - let installPrefix = process.env["TALER_PREFIX"]; + let installPrefix = process.env[prefixVarname]; if (!installPrefix) { /* Try to locate install path based on the location * of the taler-config binary. */ - const path = which("taler-config"); + const path = which(installPathBinary); if (path) { installPrefix = nodejs_fs.realpathSync( nodejs_path.dirname(path) + "/..", @@ -695,12 +751,12 @@ export class Configuration { this.setStringSystemDefault( "PATHS", "LIBEXECDIR", - `${installPrefix}/taler/libexec/`, + `${installPrefix}/${projectName}/libexec/`, ); this.setStringSystemDefault( "PATHS", "DOCDIR", - `${installPrefix}/share/doc/taler/`, + `${installPrefix}/share/doc/${projectName}/`, ); this.setStringSystemDefault( "PATHS", @@ -717,58 +773,80 @@ export class Configuration { this.setStringSystemDefault( "PATHS", "LIBDIR", - `${installPrefix}/lib/taler/`, + `${installPrefix}/lib/${projectName}/`, ); this.setStringSystemDefault( "PATHS", "DATADIR", - `${installPrefix}/share/taler/`, + `${installPrefix}/share/${projectName}/`, ); this.loadDefaultsFromDir(baseConfigDir); } - getDefaultConfigFilename(): string | undefined { + private findDefaultConfigFilename(): string | undefined { const xdg = process.env["XDG_CONFIG_HOME"]; const home = process.env["HOME"]; let fn: string | undefined; + const { projectName, componentName } = this.configSource; if (xdg) { - fn = nodejs_path.join(xdg, "taler.conf"); + fn = nodejs_path.join(xdg, `${componentName}.conf`); } else if (home) { - fn = nodejs_path.join(home, ".config/taler.conf"); + fn = nodejs_path.join(home, `.config/${componentName}.conf`); } if (fn && nodejs_fs.existsSync(fn)) { return fn; } - const etc1 = "/etc/taler.conf"; + const etc1 = `/etc/${componentName}.conf`; if (nodejs_fs.existsSync(etc1)) { return etc1; } - const etc2 = "/etc/taler/taler.conf"; + const etc2 = `/etc/${projectName}/${componentName}.conf`; if (nodejs_fs.existsSync(etc2)) { return etc2; } return undefined; } - static load(filename?: string): Configuration { - const cfg = new Configuration(); + static load( + filename?: string, + configSource?: ConfigSource | string, + ): Configuration { + let cs: ConfigSource; + if (configSource == null) { + cs = defaultConfigSource; + } else if (typeof configSource === "string") { + if (configSource in ConfigSources) { + cs = ConfigSources[configSource as keyof typeof ConfigSources]; + } else { + throw Error("invalid config source"); + } + } else { + cs = configSource; + } + const cfg = new Configuration(cs); cfg.loadDefaults(); if (filename) { cfg.loadFromFilename(filename, false); + cfg.hintEntrypoint = filename; } else { - const fn = cfg.getDefaultConfigFilename(); + const fn = cfg.findDefaultConfigFilename(); if (fn) { // It's the default filename for the main config file, // but we don't consider the values default values. cfg.loadFromFilename(fn, false); + cfg.hintEntrypoint = fn; } } - cfg.hintEntrypoint = filename; return cfg; } stringify(opts: StringifyOptions = {}): string { + if (opts.excludeDefaults && this.entrypointIsComplex) { + throw Error( + "unable to do diff serialization of config file, as entry point contains complex directives", + ); + } let s = ""; if (opts.diagnostics) { s += "# Configuration file diagnostics\n"; @@ -824,7 +902,20 @@ export class Configuration { return s; } - write(filename: string, opts: { excludeDefaults?: boolean } = {}): void { + write(opts: { excludeDefaults?: boolean } = {}): void { + const filename = this.hintEntrypoint; + if (!filename) { + throw Error( + "unknown configuration entrypoing, unable to write back config file", + ); + } + nodejs_fs.writeFileSync( + filename, + this.stringify({ excludeDefaults: opts.excludeDefaults }), + ); + } + + writeTo(filename: string, opts: { excludeDefaults?: boolean } = {}): void { nodejs_fs.writeFileSync( filename, this.stringify({ excludeDefaults: opts.excludeDefaults }), -- cgit v1.2.3