From 99bab65ff772b6649f6c459920d6c4c53b6db0a8 Mon Sep 17 00:00:00 2001 From: solimant Date: Mon, 11 Mar 2024 05:11:22 +0000 Subject: [PATCH 1/2] Support env var parameter substitution Signed-off-by: solimant --- .changeset/tricky-clouds-wait.md | 5 ++++ docs/conf/writing.md | 12 +++++++++ .../sources/transform/substitution.test.ts | 27 +++++++++++++++++++ .../src/sources/transform/substitution.ts | 19 +++++++++++-- 4 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 .changeset/tricky-clouds-wait.md diff --git a/.changeset/tricky-clouds-wait.md b/.changeset/tricky-clouds-wait.md new file mode 100644 index 0000000000..5a28f94fa7 --- /dev/null +++ b/.changeset/tricky-clouds-wait.md @@ -0,0 +1,5 @@ +--- +'@backstage/config-loader': minor +--- + +Support parameter substitution for environment variables diff --git a/docs/conf/writing.md b/docs/conf/writing.md index 48104699c7..b89a99aa09 100644 --- a/docs/conf/writing.md +++ b/docs/conf/writing.md @@ -199,6 +199,18 @@ configuration value will evaluate to `undefined`. The substitution syntax can be escaped using `$${...}`, which will be resolved as `${...}`. +Parameter substitution syntax (e.g. `${MY_VAR:-default-value}`) is also +supported to provide a default or fallback value for a given environment +variable if it is unset, or is declared but has no value. For example: + +```yaml +app: + baseUrl: https://${HOST:-http://localhost:3000} +``` + +In the above example, when `HOST` is unset or has no value, it will be +substituted with `http://localhost:3000`. + ## Combining Includes and Environment Variable Substitution The Includes and Environment Variable Substitutions can be combined to do diff --git a/packages/config-loader/src/sources/transform/substitution.test.ts b/packages/config-loader/src/sources/transform/substitution.test.ts index ff1c02174b..f7645c3cc4 100644 --- a/packages/config-loader/src/sources/transform/substitution.test.ts +++ b/packages/config-loader/src/sources/transform/substitution.test.ts @@ -55,6 +55,12 @@ describe('substituteTransform', () => { applied: true, value: 'hello my-secret', }); + await expect( + substituteTransform('hello ${SECRET:-default-secret}', { dir: '/' }), + ).resolves.toEqual({ + applied: true, + value: 'hello my-secret', + }); await expect( substituteTransform('${SECRET } $${} ${TOKEN }', { dir: '/' }), ).resolves.toEqual({ applied: true, value: 'my-secret ${} my-token' }); @@ -64,6 +70,24 @@ describe('substituteTransform', () => { applied: true, value: undefined, }); + await expect( + substituteTransform('foo ${MISSING:-}', { dir: '/' }), + ).resolves.toEqual({ + applied: true, + value: undefined, + }); + await expect( + substituteTransform('foo ${MISSING:-default-value}', { dir: '/' }), + ).resolves.toEqual({ + applied: true, + value: 'foo default-value', + }); + await expect( + substituteTransform('foo ${MISSING:-default:-value}', { dir: '/' }), + ).resolves.toEqual({ + applied: true, + value: 'foo default:-value', + }); await expect( substituteTransform('empty substitute ${}', { dir: '/' }), ).resolves.toEqual({ @@ -73,6 +97,9 @@ describe('substituteTransform', () => { await expect( substituteTransform('foo ${MISSING} ${SECRET}', { dir: '/' }), ).resolves.toEqual({ applied: true, value: undefined }); + await expect( + substituteTransform('foo ${MISSING:-} ${SECRET}', { dir: '/' }), + ).resolves.toEqual({ applied: true, value: undefined }); await expect( substituteTransform('foo ${SECRET} ${SECRET}', { dir: '/' }), ).resolves.toEqual({ applied: true, value: 'foo my-secret my-secret' }); diff --git a/packages/config-loader/src/sources/transform/substitution.ts b/packages/config-loader/src/sources/transform/substitution.ts index f10e5dc8e7..bc13100dd8 100644 --- a/packages/config-loader/src/sources/transform/substitution.ts +++ b/packages/config-loader/src/sources/transform/substitution.ts @@ -21,7 +21,9 @@ import { SubstitutionFunc } from '../types'; /** * A environment variable substitution transform that transforms e.g. 'token ${MY_TOKEN}' * to 'token abc' if MY_TOKEN is 'abc'. If any of the substituted variables are undefined, - * the entire expression ends up undefined. + * the entire expression ends up undefined. Additionally, supports parameter substitution + * syntax to provide a default or fallback value for a given environment variable if it is + * unset; e.g. 'token ${MY_TOKEN:-xyz}' transforms to 'token xyz' if MY_TOKEN is unset. */ export function createSubstitutionTransform( env: SubstitutionFunc, @@ -37,7 +39,20 @@ export function createSubstitutionTransform( if (part.startsWith('$$')) { parts[i] = part.slice(1); } else { - parts[i] = await env(part.slice(2, -1).trim()); + const indexOfFallbackSeparator = part.indexOf(':-'); + + if (indexOfFallbackSeparator > -1) { + const envVarValue = await env( + part.slice(2, indexOfFallbackSeparator).trim(), + ); + const fallbackValue = part + .slice(indexOfFallbackSeparator + ':-'.length, -1) + .trim(); + + parts[i] = envVarValue || fallbackValue || undefined; + } else { + parts[i] = await env(part.slice(2, -1).trim()); + } } } From 5496dd994bfb5bd0c011aa32094ebd9b60757dcd Mon Sep 17 00:00:00 2001 From: solimant Date: Tue, 12 Mar 2024 11:19:45 -0400 Subject: [PATCH 2/2] Update docs/conf/writing.md Co-authored-by: Patrik Oldsberg Signed-off-by: solimant --- docs/conf/writing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf/writing.md b/docs/conf/writing.md index b89a99aa09..6e827c075e 100644 --- a/docs/conf/writing.md +++ b/docs/conf/writing.md @@ -205,11 +205,11 @@ variable if it is unset, or is declared but has no value. For example: ```yaml app: - baseUrl: https://${HOST:-http://localhost:3000} + baseUrl: https://${HOST:-localhost:3000} ``` In the above example, when `HOST` is unset or has no value, it will be -substituted with `http://localhost:3000`. +substituted with `localhost:3000`. ## Combining Includes and Environment Variable Substitution