Merge pull request #1860 from spotify/rugvip/config
config,config-loader: add support for suffixed config files and multiple roots
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const pathExists = jest.fn();
|
||||
|
||||
jest.mock('fs-extra', () => ({ pathExists }));
|
||||
|
||||
import { resolveStaticConfig } from './resolver';
|
||||
|
||||
describe('resolveStaticConfig', () => {
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should resolve no files for empty roots', async () => {
|
||||
const resolved = await resolveStaticConfig({
|
||||
env: 'development',
|
||||
rootPaths: [],
|
||||
});
|
||||
|
||||
expect(resolved).toEqual([]);
|
||||
expect(pathExists).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should resolve a single app-config', async () => {
|
||||
pathExists.mockImplementation(async (path: string) =>
|
||||
['/repo/app-config.yaml'].includes(path),
|
||||
);
|
||||
const resolved = await resolveStaticConfig({
|
||||
env: 'development',
|
||||
rootPaths: ['/repo'],
|
||||
});
|
||||
|
||||
expect(resolved).toEqual(['/repo/app-config.yaml']);
|
||||
expect(pathExists).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it('should resolve a app-configs in different directories', async () => {
|
||||
pathExists.mockImplementation(async (path: string) =>
|
||||
['/repo/app-config.yaml', '/repo/packages/a/app-config.yaml'].includes(
|
||||
path,
|
||||
),
|
||||
);
|
||||
const resolved = await resolveStaticConfig({
|
||||
env: 'development',
|
||||
rootPaths: [
|
||||
'/repo',
|
||||
'/other-repo',
|
||||
'/repo/packages/a',
|
||||
'/repo/packages/b',
|
||||
],
|
||||
});
|
||||
|
||||
expect(resolved).toEqual([
|
||||
'/repo/app-config.yaml',
|
||||
'/repo/packages/a/app-config.yaml',
|
||||
]);
|
||||
expect(pathExists).toHaveBeenCalledTimes(16);
|
||||
});
|
||||
|
||||
it('should resolve env and local configs', async () => {
|
||||
pathExists.mockImplementation(async (path: string) =>
|
||||
[
|
||||
'/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',
|
||||
].includes(path),
|
||||
);
|
||||
const resolved = await resolveStaticConfig({
|
||||
env: 'development',
|
||||
rootPaths: ['/repo', '/repo/packages/a'],
|
||||
});
|
||||
|
||||
expect(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',
|
||||
]);
|
||||
expect(pathExists).toHaveBeenCalledTimes(8);
|
||||
});
|
||||
|
||||
it('resolves suffixed configs in the correct order', async () => {
|
||||
pathExists.mockImplementation(async () => true);
|
||||
const resolved = await resolveStaticConfig({
|
||||
env: 'production',
|
||||
rootPaths: ['/repo'],
|
||||
});
|
||||
|
||||
expect(resolved).toEqual([
|
||||
'/repo/app-config.yaml',
|
||||
'/repo/app-config.local.yaml',
|
||||
'/repo/app-config.production.yaml',
|
||||
'/repo/app-config.production.local.yaml',
|
||||
]);
|
||||
expect(pathExists).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
});
|
||||
@@ -15,21 +15,45 @@
|
||||
*/
|
||||
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import { pathExists } from 'fs-extra';
|
||||
|
||||
type ResolveOptions = {
|
||||
// Root path for search for app-config.yaml
|
||||
rootPath: string;
|
||||
// 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
|
||||
* NODE_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[]> {
|
||||
// TODO: We'll want this to be a bit more elaborate, probably adding configs for
|
||||
// specific env, and maybe local config for plugins.
|
||||
const configPath = resolvePath(options.rootPath, 'app-config.yaml');
|
||||
const filePaths = [
|
||||
`app-config.yaml`,
|
||||
`app-config.local.yaml`,
|
||||
`app-config.${options.env}.yaml`,
|
||||
`app-config.${options.env}.local.yaml`,
|
||||
];
|
||||
|
||||
return [configPath];
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -25,8 +25,11 @@ import {
|
||||
} from './lib';
|
||||
|
||||
export type LoadConfigOptions = {
|
||||
// Root path for search for app-config.yaml
|
||||
rootPath: string;
|
||||
// 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;
|
||||
|
||||
// Whether to read secrets or omit them, defaults to false.
|
||||
shouldReadSecrets?: boolean;
|
||||
@@ -63,8 +66,6 @@ export async function loadConfig(
|
||||
): Promise<AppConfig[]> {
|
||||
const configs = [];
|
||||
|
||||
configs.push(...readEnv(process.env));
|
||||
|
||||
const configPaths = await resolveStaticConfig(options);
|
||||
|
||||
try {
|
||||
@@ -86,5 +87,7 @@ export async function loadConfig(
|
||||
);
|
||||
}
|
||||
|
||||
configs.push(...readEnv(process.env));
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user