feat: added disable config watching flag

Signed-off-by: Antonio Musolino <antoniomusolino007@gmail.com>
This commit is contained in:
Antonio Musolino
2023-09-01 15:20:29 +02:00
parent 16864de5e0
commit a4617c422a
9 changed files with 84 additions and 42 deletions
+6
View File
@@ -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`.
+1
View File
@@ -190,6 +190,7 @@ export function loadBackendConfig(options: {
remote?: LoadConfigOptionsRemote;
argv: string[];
additionalConfigs?: AppConfig[];
watch?: boolean;
}): Promise<{
config: Config;
}>;
+29 -23
View File
@@ -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(', ')}`,
+1
View File
@@ -552,6 +552,7 @@ export function loadBackendConfig(options: {
remote?: LoadConfigOptionsRemote;
additionalConfigs?: AppConfig[];
argv: string[];
watch?: boolean;
}): Promise<Config>;
// @public (undocumented)
+1
View File
@@ -36,6 +36,7 @@ export async function loadBackendConfig(options: {
remote?: LoadConfigOptionsRemote;
additionalConfigs?: AppConfig[];
argv: string[];
watch?: boolean;
}): Promise<Config> {
const secretEnumerator = await createConfigSecretEnumerator({
logger: options.logger,
+3
View File
@@ -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
+1
View File
@@ -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',
@@ -71,6 +71,7 @@ export interface ClosableConfig extends Config {
* @public
*/
export interface BaseConfigSourcesOptions {
watch?: boolean;
rootDir?: string;
remote?: Pick<RemoteConfigSourceOptions, 'reloadInterval'>;
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,
}),
@@ -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<string>();
const watcher = chokidar.watch(this.#path, {
usePolling: process.env.NODE_ENV === 'test',
});
let watchedPaths: Array<string> | 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<string>();
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<ConfigSourceData[]> => {
// 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() };
}
}