config-loader: apply review feedback

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-04-06 17:37:35 +02:00
parent 4caade51a0
commit ae57dd4ac0
11 changed files with 60 additions and 29 deletions
+1 -1
View File
@@ -55,7 +55,7 @@ export const cacheServiceFactory: () => ServiceFactory<CacheClient, 'plugin'>;
// @public (undocumented)
export interface ConfigFactoryOptions {
argv?: string[];
remote?: Pick<RemoteConfigSourceOptions, 'reloadIntervalSeconds'>;
remote?: Pick<RemoteConfigSourceOptions, 'reloadInterval'>;
}
// @public (undocumented)
@@ -33,7 +33,7 @@ export interface ConfigFactoryOptions {
/**
* Enables and sets options for remote configuration loading.
*/
remote?: Pick<RemoteConfigSourceOptions, 'reloadIntervalSeconds'>;
remote?: Pick<RemoteConfigSourceOptions, 'reloadInterval'>;
}
/** @public */
+3 -2
View File
@@ -5,6 +5,7 @@
```ts
import { AppConfig } from '@backstage/config';
import { Config } from '@backstage/config';
import { HumanDuration } from '@backstage/types';
import { JsonObject } from '@backstage/types';
import { JSONSchema7 } from 'json-schema';
import { Observable } from '@backstage/types';
@@ -31,7 +32,7 @@ export interface AsyncConfigSourceIterator
// @public
export interface BaseConfigSourcesOptions {
// (undocumented)
remote?: Pick<RemoteConfigSourceOptions, 'reloadIntervalSeconds'>;
remote?: Pick<RemoteConfigSourceOptions, 'reloadInterval'>;
// (undocumented)
rootDir?: string;
// (undocumented)
@@ -246,7 +247,7 @@ export class RemoteConfigSource implements ConfigSource {
// @public
export interface RemoteConfigSourceOptions {
reloadIntervalSeconds?: number;
reloadInterval?: HumanDuration;
substitutionFunc?: EnvFunc;
url: string;
}
+2 -2
View File
@@ -44,8 +44,8 @@
"json-schema": "^0.4.0",
"json-schema-merge-allof": "^0.8.1",
"json-schema-traverse": "^1.0.0",
"lodash": "^4.14.151",
"minimist": "^1.2.8",
"lodash": "^4.17.21",
"minimist": "^1.2.5",
"node-fetch": "^2.6.7",
"typescript-json-schema": "^0.55.0",
"yaml": "^2.0.0",
+3 -1
View File
@@ -104,7 +104,9 @@ export async function loadConfig(
): Promise<LoadConfigResult> {
const source = ConfigSources.default({
substitutionFunc: options.experimentalEnvFunc,
remote: options.remote,
remote: options.remote && {
reloadInterval: { seconds: options.remote.reloadIntervalSeconds },
},
rootDir: options.configRoot,
argv: options.configTargets.flatMap(t => [
'--config',
@@ -135,14 +135,14 @@ describe('ConfigSources', () => {
ConfigSources.defaultForTargets({
rootDir: '/',
targets: [{ type: 'url', target: 'http://example.com/config.yaml' }],
remote: { reloadIntervalSeconds: 5 },
remote: { reloadInterval: { minutes: 2 } },
}),
),
).toEqual([
{
name: 'RemoteConfigSource',
url: 'http://example.com/config.yaml',
reloadIntervalSeconds: 5,
reloadInterval: { minutes: 2 },
},
]);
});
@@ -72,7 +72,7 @@ export interface ClosableConfig extends Config {
*/
export interface BaseConfigSourcesOptions {
rootDir?: string;
remote?: Pick<RemoteConfigSourceOptions, 'reloadIntervalSeconds'>;
remote?: Pick<RemoteConfigSourceOptions, 'reloadInterval'>;
substitutionFunc?: SubstitutionFunc;
}
@@ -112,8 +112,12 @@ export class ConfigSources {
const args: string[] = [parseArgs(argv).config].flat().filter(Boolean);
return args.map(target => {
try {
// eslint-disable-next-line no-new
new URL(target);
const url = new URL(target);
// Some file paths are valid relative URLs, so check if the host is empty too
if (!url.host) {
return { type: 'path', target };
}
return { type: 'url', target };
} catch {
return { type: 'path', target };
@@ -151,7 +155,7 @@ export class ConfigSources {
return RemoteConfigSource.create({
url: arg.target,
substitutionFunc: options.substitutionFunc,
reloadIntervalSeconds: options.remote.reloadIntervalSeconds,
reloadInterval: options.remote.reloadInterval,
});
}
return FileConfigSource.create({
@@ -147,9 +147,11 @@ export class FileConfigSource implements ConfigSource {
}
};
signal?.addEventListener('abort', () => {
const onAbort = () => {
signal?.removeEventListener('abort', onAbort);
watcher.close();
});
};
signal?.addEventListener('abort', onAbort);
yield { configs: await readConfigFile() };
@@ -74,7 +74,7 @@ app:
const source = RemoteConfigSource.create({
url: 'http://localhost/config.yaml',
reloadIntervalSeconds: 0,
reloadInterval: { seconds: 0 },
});
await expect(readN(source, 2)).resolves.toEqual([
@@ -15,7 +15,7 @@
*/
import { ResponseError } from '@backstage/errors';
import { JsonObject } from '@backstage/types';
import { HumanDuration, JsonObject } from '@backstage/types';
import isEqual from 'lodash/isEqual';
import fetch from 'node-fetch';
import yaml from 'yaml';
@@ -27,7 +27,28 @@ import {
ReadConfigDataOptions,
} from './types';
const DEFAULT_RELOAD_INTERVAL_SECONDS = 60;
const DEFAULT_RELOAD_INTERVAL = { seconds: 60 };
function durationToMs(duration: HumanDuration): number {
const {
years = 0,
months = 0,
weeks = 0,
days = 0,
hours = 0,
minutes = 0,
seconds = 0,
milliseconds = 0,
} = duration;
const totalDays = years * 365 + months * 30 + weeks * 7 + days;
const totalHours = totalDays * 24 + hours;
const totalMinutes = totalHours * 60 + minutes;
const totalSeconds = totalMinutes * 60 + seconds;
const totalMilliseconds = totalSeconds * 1000 + milliseconds;
return totalMilliseconds;
}
/**
* Options for {@link RemoteConfigSource.create}.
@@ -45,7 +66,7 @@ export interface RemoteConfigSourceOptions {
*
* Set to Infinity to disable reloading.
*/
reloadIntervalSeconds?: number;
reloadInterval?: HumanDuration;
/**
* A substitution function to use instead of the default environment substitution.
@@ -78,13 +99,14 @@ export class RemoteConfigSource implements ConfigSource {
}
readonly #url: string;
readonly #reloadIntervalSeconds: number;
readonly #reloadIntervalMs: number;
readonly #transformer: ConfigTransformer;
private constructor(options: RemoteConfigSourceOptions) {
this.#url = options.url;
this.#reloadIntervalSeconds =
options.reloadIntervalSeconds ?? DEFAULT_RELOAD_INTERVAL_SECONDS;
this.#reloadIntervalMs = durationToMs(
options.reloadInterval ?? DEFAULT_RELOAD_INTERVAL,
);
this.#transformer = createConfigTransformer({
substitutionFunc: options.substitutionFunc,
});
@@ -98,12 +120,12 @@ export class RemoteConfigSource implements ConfigSource {
yield { configs: [{ data, context: this.#url }] };
for (;;) {
await this.#wait(options?.signal);
if (options?.signal?.aborted) {
return;
}
await this.#wait(options?.signal);
try {
const newData = await this.#load(options?.signal);
if (newData && !isEqual(data, newData)) {
@@ -146,7 +168,7 @@ export class RemoteConfigSource implements ConfigSource {
async #wait(signal?: AbortSignal) {
return new Promise<void>(resolve => {
const timeoutId = setTimeout(onDone, this.#reloadIntervalSeconds * 1000);
const timeoutId = setTimeout(onDone, this.#reloadIntervalMs);
signal?.addEventListener('abort', onDone);
function onDone() {
+4 -4
View File
@@ -3940,8 +3940,8 @@ __metadata:
json-schema: ^0.4.0
json-schema-merge-allof: ^0.8.1
json-schema-traverse: ^1.0.0
lodash: ^4.14.151
minimist: ^1.2.8
lodash: ^4.17.21
minimist: ^1.2.5
mock-fs: ^5.1.0
msw: ^1.0.0
node-fetch: ^2.6.7
@@ -29512,7 +29512,7 @@ __metadata:
languageName: node
linkType: hard
"lodash@npm:4.17.21, lodash@npm:^4.14.151, lodash@npm:^4.15.0, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:^4.7.0, lodash@npm:~4.17.0, lodash@npm:~4.17.15":
"lodash@npm:4.17.21, lodash@npm:^4.15.0, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:^4.7.0, lodash@npm:~4.17.0, lodash@npm:~4.17.15":
version: 4.17.21
resolution: "lodash@npm:4.17.21"
checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7
@@ -30756,7 +30756,7 @@ __metadata:
languageName: node
linkType: hard
"minimist@npm:>=1.2.2, minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.7, minimist@npm:^1.2.8":
"minimist@npm:>=1.2.2, minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.7":
version: 1.2.8
resolution: "minimist@npm:1.2.8"
checksum: 75a6d645fb122dad29c06a7597bddea977258957ed88d7a6df59b5cd3fe4a527e253e9bbf2e783e4b73657f9098b96a5fe96ab8a113655d4109108577ecf85b0