config-loader: switch to using --config options to load in config
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
}),
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user