config-loader: switch to using --config options to load in config

This commit is contained in:
Patrik Oldsberg
2020-10-20 13:08:05 +02:00
parent ad221957cd
commit 8c2b76e45f
27 changed files with 200 additions and 302 deletions
-1
View File
@@ -14,7 +14,6 @@
* limitations under the License.
*/
export { resolveStaticConfig } from './resolver';
export { readConfigFile } from './reader';
export { readEnvConfig } from './env';
export { readSecret } from './secrets';
@@ -1,119 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import mockFs from 'mock-fs';
import { resolveStaticConfig } from './resolver';
function normalizePaths(paths: string[]) {
return paths.map(p =>
p
.replace(/^[a-z]:/i, '')
.split('\\')
.join('/'),
);
}
describe('resolveStaticConfig', () => {
afterEach(() => {
mockFs.restore();
});
it('should resolve no files for empty roots', async () => {
mockFs({});
const resolved = await resolveStaticConfig({
env: 'development',
rootPaths: [],
});
expect(normalizePaths(resolved)).toEqual([]);
});
it('should resolve a single app-config', async () => {
mockFs({ '/repo/app-config.yaml': '' });
const resolved = await resolveStaticConfig({
env: 'development',
rootPaths: ['/repo'],
});
expect(normalizePaths(resolved)).toEqual(['/repo/app-config.yaml']);
});
it('should resolve a app-configs in different directories', async () => {
mockFs({
'/repo/app-config.yaml': '',
'/repo/packages/a/app-config.yaml': '',
});
const resolved = await resolveStaticConfig({
env: 'development',
rootPaths: [
'/repo',
'/other-repo',
'/repo/packages/a',
'/repo/packages/b',
],
});
expect(normalizePaths(resolved)).toEqual([
'/repo/app-config.yaml',
'/repo/packages/a/app-config.yaml',
]);
});
it('should resolve env and local configs', async () => {
mockFs({
'/repo/app-config.yaml': '',
'/repo/app-config.local.yaml': '',
'/repo/app-config.production.yaml': '',
'/repo/app-config.production.local.yaml': '',
'/repo/app-config.development.local.yaml': '',
'/repo/packages/a/app-config.development.yaml': '',
'/repo/packages/a/app-config.local.yaml': '',
});
const resolved = await resolveStaticConfig({
env: 'development',
rootPaths: ['/repo', '/repo/packages/a'],
});
expect(normalizePaths(resolved)).toEqual([
'/repo/app-config.yaml',
'/repo/app-config.local.yaml',
'/repo/app-config.development.local.yaml',
'/repo/packages/a/app-config.local.yaml',
'/repo/packages/a/app-config.development.yaml',
]);
});
it('resolves suffixed configs in the correct order', async () => {
mockFs({
'/repo/app-config.yaml': '',
'/repo/app-config.local.yaml': '',
'/repo/app-config.production.yaml': '',
'/repo/app-config.production.local.yaml': '',
});
const resolved = await resolveStaticConfig({
env: 'production',
rootPaths: ['/repo'],
});
expect(normalizePaths(resolved)).toEqual([
'/repo/app-config.yaml',
'/repo/app-config.local.yaml',
'/repo/app-config.production.yaml',
'/repo/app-config.production.local.yaml',
]);
});
});
@@ -1,59 +0,0 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { resolve as resolvePath } from 'path';
import { pathExists } from 'fs-extra';
type ResolveOptions = {
// Root paths to search for config files. Config from earlier paths has lower priority.
rootPaths: string[];
// The environment that we're loading config for, e.g. 'development', 'production'.
env: string;
};
/**
* Resolves all configuration files that should be loaded in the given environment.
*
* For each root directory, search for the default app-config.yaml, along with suffixed
* APP_ENV and local variants, e.g. app-config.production.yaml or app-config.development.local.yaml
*
* The priority order of config loaded through suffixes is `env > local > none`, meaning that
* for example app-config.development.yaml has higher priority than `app-config.local.yaml`.
*
*/
export async function resolveStaticConfig(
options: ResolveOptions,
): Promise<string[]> {
const filePaths = [
`app-config.yaml`,
`app-config.local.yaml`,
`app-config.${options.env}.yaml`,
`app-config.${options.env}.local.yaml`,
];
const resolvedPaths = [];
for (const rootPath of options.rootPaths) {
for (const filePath of filePaths) {
const path = resolvePath(rootPath, filePath);
if (await pathExists(path)) {
resolvedPaths.push(path);
}
}
}
return resolvedPaths;
}
+34 -4
View File
@@ -38,10 +38,31 @@ describe('loadConfig', () => {
mockFs.restore();
});
it('load config from default path', async () => {
await expect(
loadConfig({
configRoot: '/root',
configPaths: [],
env: 'production',
shouldReadSecrets: false,
}),
).resolves.toEqual([
{
context: 'app-config.yaml',
data: {
app: {
title: 'Example App',
},
},
},
]);
});
it('loads config without secrets', async () => {
await expect(
loadConfig({
rootPaths: ['/root'],
configRoot: '/root',
configPaths: ['/root/app-config.yaml'],
env: 'production',
shouldReadSecrets: false,
}),
@@ -60,7 +81,8 @@ describe('loadConfig', () => {
it('loads config with secrets', async () => {
await expect(
loadConfig({
rootPaths: ['/root'],
configRoot: '/root',
configPaths: ['/root/app-config.yaml'],
env: 'production',
shouldReadSecrets: true,
}),
@@ -80,7 +102,11 @@ describe('loadConfig', () => {
it('loads development config without secrets', async () => {
await expect(
loadConfig({
rootPaths: ['/root'],
configRoot: '/root',
configPaths: [
'/root/app-config.yaml',
'/root/app-config.development.yaml',
],
env: 'development',
shouldReadSecrets: false,
}),
@@ -105,7 +131,11 @@ describe('loadConfig', () => {
it('loads development config with secrets', async () => {
await expect(
loadConfig({
rootPaths: ['/root'],
configRoot: '/root',
configPaths: [
'/root/app-config.yaml',
'/root/app-config.development.yaml',
],
env: 'development',
shouldReadSecrets: true,
}),
+33 -11
View File
@@ -15,20 +15,18 @@
*/
import fs from 'fs-extra';
import { resolve as resolvePath, dirname } from 'path';
import { resolve as resolvePath, dirname, isAbsolute } from 'path';
import { AppConfig, JsonObject } from '@backstage/config';
import {
resolveStaticConfig,
readConfigFile,
readEnvConfig,
readSecret,
} from './lib';
import { readConfigFile, readEnvConfig, readSecret } from './lib';
export type LoadConfigOptions = {
// Root paths to search for config files. Config from earlier paths has lower priority.
rootPaths: string[];
// The root directory of the config loading context. Used to find default configs.
configRoot: string;
// The environment that we're loading config for, e.g. 'development', 'production'.
// Absolute paths to load config files from. Configs from earlier paths have lower priority.
configPaths: string[];
// TODO(Rugvip): This will be removed in the future, but for now we use it to warn about possible mistakes.
env: string;
// Whether to read secrets or omit them, defaults to false.
@@ -77,13 +75,37 @@ export async function loadConfig(
options: LoadConfigOptions,
): Promise<AppConfig[]> {
const configs = [];
const { configRoot } = options;
const configPaths = options.configPaths.slice();
const configPaths = await resolveStaticConfig(options);
// If no paths are provided, we default to reading
// `app-config.yaml` and, if it exists, `app-config.local.yaml`
if (configPaths.length === 0) {
configPaths.push(resolvePath(configRoot, 'app-config.yaml'));
const localConfig = resolvePath(configRoot, 'app-config.local.yaml');
if (await fs.pathExists(localConfig)) {
configPaths.push(localConfig);
}
const envFile = `app-config.${options.env}.yaml`;
if (await fs.pathExists(resolvePath(configRoot, envFile))) {
console.error(
`Env config file '${envFile}' is not loaded as APP_ENV and NODE_ENV-based config loading has been removed`,
);
console.error(
`To load the config file, use --config <path>, listing every config file that you want to load`,
);
}
}
try {
const secretPaths = new Set<string>();
for (const configPath of configPaths) {
if (!isAbsolute(configPath)) {
throw new Error(`Config load path is not absolute: '${configPath}'`);
}
const config = await readConfigFile(
configPath,
new Context({