diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index 26e258a588..ba577c4637 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 * as yup from 'yup'; import yaml from 'yaml'; -import { resolve as resolvePath, dirname } from 'path'; +import { resolve as resolvePath, dirname, extname } from 'path'; import { AppConfig, JsonObject, JsonValue } from '@backstage/config'; import { findRootPath } from './paths'; import { LoadConfigOptions } from './types'; @@ -103,7 +103,12 @@ type EnvSecret = { env: string; }; -type Secret = FileSecret | EnvSecret; +type DataSecret = { + data: string; + path: string | string[]; +}; + +type Secret = FileSecret | EnvSecret | DataSecret; const secretLoaderSchemas = { file: yup.object({ @@ -112,6 +117,15 @@ const secretLoaderSchemas = { env: yup.object({ env: yup.string().required(), }), + data: yup.object({ + data: yup.string().required(), + path: yup.lazy(value => { + if (typeof value === 'string') { + return yup.string().required(); + } + return yup.array().of(yup.string().required()).required(); + }), + }), }; const secretSchema = yup.lazy(value => { @@ -135,6 +149,14 @@ const secretSchema = yup.lazy(value => { ); }); +const dataSecretParser: { + [ext in string]: (content: string) => Promise; +} = { + '.json': async content => JSON.parse(content), + '.yaml': async content => yaml.parse(content), + '.yml': async content => yaml.parse(content), +}; + // A thing to make sure we've narrowed the type down to never function isNever() { return void 0 as T; @@ -156,6 +178,30 @@ export async function readSecret( if ('env' in secret) { return ctx.env[secret.env]; } + if ('data' in secret) { + const ext = extname(secret.data); + const parser = dataSecretParser[ext]; + if (!parser) { + throw new Error(`No data secret parser available for extension ${ext}`); + } + + const content = await ctx.readFile(secret.data); + + const { path } = secret; + const parts = typeof path === 'string' ? path.split('.') : path; + + let value: JsonValue = await parser(content); + for (const [index, part] of parts.entries()) { + if (!isObject(value)) { + const errPath = parts.slice(0, index).join('.'); + throw new Error( + `Value is not an object at ${errPath} in ${secret.data}`, + ); + } + value = value[part]; + } + return String(value); + } isNever(); throw new Error('Secret was left unhandled');