diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index c8b5bb538d..6bdc954e04 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -17,7 +17,7 @@ import fs from 'fs-extra'; import yaml from 'yaml'; import { resolve as resolvePath } from 'path'; -import { AppConfig, JsonObject } from '@backstage/config'; +import { AppConfig, JsonObject, JsonValue } from '@backstage/config'; import { findRootPath } from './paths'; import { LoadConfigOptions } from './types'; @@ -77,7 +77,70 @@ export function readEnv(env: { return config ? [config] : []; } -export async function readStaticConfig( +type ReadFileFunc = (path: string) => Promise; + +function isObject(obj: JsonValue | undefined): obj is JsonObject { + if (typeof obj !== 'object') { + return false; + } else if (Array.isArray(obj)) { + return false; + } + return obj !== null; +} + +export async function readConfigFile(filePath: string, readFile: ReadFileFunc) { + const configYaml = await readFile(filePath); + const config = yaml.parse(configYaml); + + async function transform( + obj: JsonValue, + path: string, + ): Promise { + if (typeof obj !== 'object') { + return obj; + } else if (obj === null) { + return obj; + } else if (Array.isArray(obj)) { + const arr = new Array(); + + for (const [index, value] of obj.entries()) { + const out = await transform(value, `${path}[${index}]`); + if (out !== undefined) { + arr.push(out); + } + } + + return arr; + } + + if ('$secret' in obj) { + if (!isObject(obj.$secret)) { + throw TypeError(`Secret expected object at secret ${path}.$secret`); + } + + return undefined; + } + + const out: JsonObject = {}; + + for (const [key, value] of Object.entries(obj)) { + const result = await transform(value, `${path}.${key}`); + if (result !== undefined) { + out[key] = result; + } + } + + return out; + } + + const finalConfig = await transform(config, ''); + if (!isObject(finalConfig)) { + throw new TypeError('Expected object at config root'); + } + return finalConfig; +} + +export async function loadStaticConfig( options: LoadConfigOptions, ): Promise { // TODO: We'll want this to be a bit more elaborate, probably adding configs for @@ -91,8 +154,10 @@ export async function readStaticConfig( } try { - const configYaml = await fs.readFile(configPath, 'utf8'); - const config = yaml.parse(configYaml); + const rootPath = configPath; + const config = await readConfigFile(configPath, (path: string) => { + return fs.readFile(resolvePath(rootPath, path), 'utf8'); + }); return [config]; } catch (error) { throw new Error(`Failed to read static configuration file, ${error}`); @@ -105,7 +170,7 @@ export async function loadConfig( const configs = []; configs.push(...readEnv(process.env)); - configs.push(...(await readStaticConfig(options))); + configs.push(...(await loadStaticConfig(options))); return configs; }