From a4617c422a564da1f818589db18e661da420a807 Mon Sep 17 00:00:00 2001 From: Antonio Musolino Date: Fri, 1 Sep 2023 15:20:29 +0200 Subject: [PATCH] feat: added disable config watching flag Signed-off-by: Antonio Musolino --- .changeset/stale-eagles-reply.md | 6 ++ packages/backend-app-api/api-report.md | 1 + packages/backend-app-api/src/config/config.ts | 52 +++++++++-------- packages/backend-common/api-report.md | 1 + packages/backend-common/src/config.ts | 1 + packages/config-loader/api-report.md | 3 + packages/config-loader/src/loader.ts | 1 + .../src/sources/ConfigSources.ts | 4 ++ .../src/sources/FileConfigSource.ts | 57 ++++++++++++------- 9 files changed, 84 insertions(+), 42 deletions(-) create mode 100644 .changeset/stale-eagles-reply.md diff --git a/.changeset/stale-eagles-reply.md b/.changeset/stale-eagles-reply.md new file mode 100644 index 0000000000..4584355a59 --- /dev/null +++ b/.changeset/stale-eagles-reply.md @@ -0,0 +1,6 @@ +--- +'@backstage/config-loader': patch +'@backstage/backend-app-api': patch +--- + +Added `watch` option to configuration loaders that can be used to disable file watching by setting it to `false`. diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index 3bddcc48d5..f98fb7275d 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -190,6 +190,7 @@ export function loadBackendConfig(options: { remote?: LoadConfigOptionsRemote; argv: string[]; additionalConfigs?: AppConfig[]; + watch?: boolean; }): Promise<{ config: Config; }>; diff --git a/packages/backend-app-api/src/config/config.ts b/packages/backend-app-api/src/config/config.ts index 7367767870..8ecc015b66 100644 --- a/packages/backend-app-api/src/config/config.ts +++ b/packages/backend-app-api/src/config/config.ts @@ -72,6 +72,7 @@ export async function loadBackendConfig(options: { remote?: LoadConfigOptionsRemote; argv: string[]; additionalConfigs?: AppConfig[]; + watch?: boolean; }): Promise<{ config: Config }> { const args = parseArgs(options.argv); @@ -89,30 +90,35 @@ export async function loadBackendConfig(options: { configRoot: paths.targetRoot, configTargets: configTargets, remote: options.remote, - watch: { - onChange(newConfigs) { - console.info( - `Reloaded config from ${newConfigs.map(c => c.context).join(', ')}`, - ); - const configsToMerge = [...newConfigs]; - if (options.additionalConfigs) { - configsToMerge.push(...options.additionalConfigs); - } - config.setConfig(ConfigReader.fromConfigs(configsToMerge)); - }, - stopSignal: new Promise(resolve => { - if (currentCancelFunc) { - currentCancelFunc(); - } - currentCancelFunc = resolve; + watch: + options.watch ?? true + ? { + onChange(newConfigs) { + console.info( + `Reloaded config from ${newConfigs + .map(c => c.context) + .join(', ')}`, + ); + const configsToMerge = [...newConfigs]; + if (options.additionalConfigs) { + configsToMerge.push(...options.additionalConfigs); + } + config.setConfig(ConfigReader.fromConfigs(configsToMerge)); + }, + stopSignal: new Promise(resolve => { + if (currentCancelFunc) { + currentCancelFunc(); + } + currentCancelFunc = resolve; - // TODO(Rugvip): We keep this here for now to avoid breaking the old system - // since this is re-used in backend-common - if (module.hot) { - module.hot.addDisposeHandler(resolve); - } - }), - }, + // TODO(Rugvip): We keep this here for now to avoid breaking the old system + // since this is re-used in backend-common + if (module.hot) { + module.hot.addDisposeHandler(resolve); + } + }), + } + : undefined, }); console.info( `Loaded config from ${appConfigs.map(c => c.context).join(', ')}`, diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index ab640ccd9b..7a3a65eaab 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -552,6 +552,7 @@ export function loadBackendConfig(options: { remote?: LoadConfigOptionsRemote; additionalConfigs?: AppConfig[]; argv: string[]; + watch?: boolean; }): Promise; // @public (undocumented) diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index 53b438de5d..b452d8db70 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -36,6 +36,7 @@ export async function loadBackendConfig(options: { remote?: LoadConfigOptionsRemote; additionalConfigs?: AppConfig[]; argv: string[]; + watch?: boolean; }): Promise { const secretEnumerator = await createConfigSecretEnumerator({ logger: options.logger, diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md index 8cd469d4d7..9f55a27bf7 100644 --- a/packages/config-loader/api-report.md +++ b/packages/config-loader/api-report.md @@ -27,6 +27,8 @@ export interface BaseConfigSourcesOptions { rootDir?: string; // (undocumented) substitutionFunc?: EnvFunc; + // (undocumented) + watch?: boolean; } // @public @@ -142,6 +144,7 @@ export class FileConfigSource implements ConfigSource { export interface FileConfigSourceOptions { path: string; substitutionFunc?: EnvFunc; + watch?: boolean; } // @public @deprecated diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index 8b6fd2a953..55cf123f11 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -107,6 +107,7 @@ export async function loadConfig( remote: options.remote && { reloadInterval: { seconds: options.remote.reloadIntervalSeconds }, }, + watch: Boolean(options.watch), rootDir: options.configRoot, argv: options.configTargets.flatMap(t => [ '--config', diff --git a/packages/config-loader/src/sources/ConfigSources.ts b/packages/config-loader/src/sources/ConfigSources.ts index d60e9d53e9..43123f87ea 100644 --- a/packages/config-loader/src/sources/ConfigSources.ts +++ b/packages/config-loader/src/sources/ConfigSources.ts @@ -71,6 +71,7 @@ export interface ClosableConfig extends Config { * @public */ export interface BaseConfigSourcesOptions { + watch?: boolean; rootDir?: string; remote?: Pick; substitutionFunc?: SubstitutionFunc; @@ -159,6 +160,7 @@ export class ConfigSources { }); } return FileConfigSource.create({ + watch: options.watch, path: arg.target, substitutionFunc: options.substitutionFunc, }); @@ -170,6 +172,7 @@ export class ConfigSources { argSources.push( FileConfigSource.create({ + watch: options.watch, path: defaultPath, substitutionFunc: options.substitutionFunc, }), @@ -177,6 +180,7 @@ export class ConfigSources { if (fs.pathExistsSync(localPath)) { argSources.push( FileConfigSource.create({ + watch: options.watch, path: localPath, substitutionFunc: options.substitutionFunc, }), diff --git a/packages/config-loader/src/sources/FileConfigSource.ts b/packages/config-loader/src/sources/FileConfigSource.ts index 1de0fbf2d8..d8f11fd9c7 100644 --- a/packages/config-loader/src/sources/FileConfigSource.ts +++ b/packages/config-loader/src/sources/FileConfigSource.ts @@ -39,6 +39,11 @@ export interface FileConfigSourceOptions { */ path: string; + /** + * Enable watching file + */ + watch?: boolean; + /** * A substitution function to use instead of the default environment substitution. */ @@ -89,10 +94,12 @@ export class FileConfigSource implements ConfigSource { readonly #path: string; readonly #substitutionFunc?: SubstitutionFunc; + readonly #watch?: boolean; private constructor(options: FileConfigSourceOptions) { this.#path = options.path; this.#substitutionFunc = options.substitutionFunc; + this.#watch = options.watch ?? true; } // Work is duplicated across each read, in practice that should not @@ -104,20 +111,27 @@ export class FileConfigSource implements ConfigSource { const signal = options?.signal; const configFileName = basename(this.#path); - // Keep track of watched paths, since this is simpler than resetting the watcher - const watchedPaths = new Array(); - const watcher = chokidar.watch(this.#path, { - usePolling: process.env.NODE_ENV === 'test', - }); + let watchedPaths: Array | null = null; + let watcher: FSWatcher | null = null; + + if (this.#watch) { + // Keep track of watched paths, since this is simpler than resetting the watcher + watchedPaths = new Array(); + watcher = chokidar.watch(this.#path, { + usePolling: process.env.NODE_ENV === 'test', + }); + } const dir = dirname(this.#path); const transformer = createConfigTransformer({ substitutionFunc: this.#substitutionFunc, readFile: async path => { const fullPath = resolvePath(dir, path); - // Any files discovered while reading this config should be watched too - watcher.add(fullPath); - watchedPaths.push(fullPath); + if (watcher && watchedPaths) { + // Any files discovered while reading this config should be watched too + watcher.add(fullPath); + watchedPaths.push(fullPath); + } const data = await readFile(fullPath); if (data === undefined) { @@ -131,12 +145,15 @@ export class FileConfigSource implements ConfigSource { // This is the entry point for reading the file, called initially and on change const readConfigFile = async (): Promise => { - // We clear the watched files every time we initiate a new read - watcher.unwatch(watchedPaths); - watchedPaths.length = 0; + if (watcher && watchedPaths) { + // We clear the watched files every time we initiate a new read + watcher.unwatch(watchedPaths); + watchedPaths.length = 0; + + watcher.add(this.#path); + watchedPaths.push(this.#path); + } - watcher.add(this.#path); - watchedPaths.push(this.#path); const content = await readFile(this.#path); if (content === undefined) { throw new NotFoundError(`Config file "${this.#path}" does not exist`); @@ -157,18 +174,20 @@ export class FileConfigSource implements ConfigSource { const onAbort = () => { signal?.removeEventListener('abort', onAbort); - watcher.close(); + if (watcher) watcher.close(); }; signal?.addEventListener('abort', onAbort); yield { configs: await readConfigFile() }; - for (;;) { - const event = await this.#waitForEvent(watcher, signal); - if (event === 'abort') { - return; + if (watcher) { + for (;;) { + const event = await this.#waitForEvent(watcher, signal); + if (event === 'abort') { + return; + } + yield { configs: await readConfigFile() }; } - yield { configs: await readConfigFile() }; } }