From 5622362b3eac0f750007b0afc7f9cf3eaf88484a Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Fri, 24 Jan 2025 13:59:17 -0700 Subject: [PATCH 1/5] Add configuration to permit backend startup failure Signed-off-by: Tim Hansen --- .changeset/twenty-carrots-jog.md | 17 +++++++++++++ packages/backend-app-api/config.d.ts | 4 +++ .../src/wiring/BackendInitializer.test.ts | 24 ++++++++++++++++++ .../src/wiring/BackendInitializer.ts | 25 ++++++++++++++++--- .../src/wiring/createInitializationLogger.ts | 8 ++++++ packages/backend-app-api/src/wiring/index.ts | 6 ++++- packages/backend-app-api/src/wiring/types.ts | 15 +++++++++++ 7 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 .changeset/twenty-carrots-jog.md diff --git a/.changeset/twenty-carrots-jog.md b/.changeset/twenty-carrots-jog.md new file mode 100644 index 0000000000..9a87c403ff --- /dev/null +++ b/.changeset/twenty-carrots-jog.md @@ -0,0 +1,17 @@ +--- +'@backstage/backend-app-api': patch +--- + +Added a configuration to permit backend plugin failures on startup: + +```yaml +backend: + ... + startup: + plugin-x: + optional: true +``` + +This configuration permits `plugin-x` to fail on startup. Omitting the `startup` +configuration matches the previous behavior, wherein any individual plugin +failure is fatal to backend startup. diff --git a/packages/backend-app-api/config.d.ts b/packages/backend-app-api/config.d.ts index b5e56af67c..7ffa882fc4 100644 --- a/packages/backend-app-api/config.d.ts +++ b/packages/backend-app-api/config.d.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { BackendStartupOptions } from './src'; + export interface Config { backend?: { /** Used by the feature discovery service */ @@ -23,5 +25,7 @@ export interface Config { include?: string[]; exclude?: string[]; }; + + startup?: BackendStartupOptions; }; } diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts index 39a54747d5..304b547d15 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -560,6 +560,30 @@ describe('BackendInitializer', () => { ); }); + it('should permit startup errors for plugins marked as optional', async () => { + const init = new BackendInitializer([ + mockServices.rootLifecycle.factory(), + mockServices.rootLogger.factory(), + mockServices.rootConfig.factory({ + data: { backend: { startup: { test: { optional: true } } } }, + }), + ]); + init.add( + createBackendPlugin({ + pluginId: 'test', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw new Error('NOPE'); + }, + }); + }, + }), + ); + await init.start(); + }); + it('should forward errors when multiple plugins fail to start', async () => { const init = new BackendInitializer([]); init.add( diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index 02fad4f217..eb63ebe78b 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -24,7 +24,7 @@ import { RootLifecycleService, createServiceFactory, } from '@backstage/backend-plugin-api'; -import { ServiceOrExtensionPoint } from './types'; +import { BackendStartupOptions, ServiceOrExtensionPoint } from './types'; // Direct internal import to avoid duplication // eslint-disable-next-line @backstage/no-relative-monorepo-imports import type { @@ -321,6 +321,21 @@ export class BackendInitializer { await this.#serviceRegistry.get(coreServices.rootLogger, 'root'), ); + const rootConfig = await this.#serviceRegistry.get( + coreServices.rootConfig, + 'root', + ); + + const startupOptions = + rootConfig + ?.getOptionalConfig('backend.startup') + ?.get() ?? {}; + + // Gather backend pluginIds marked as optional, since these should not cause a startup failure + const optionalBackends = Object.entries(startupOptions) + .filter(([_, value]) => value?.optional) + .map(([key]) => key); + // All plugins are initialized in parallel const results = await Promise.allSettled( allPluginIds.map(async pluginId => { @@ -392,8 +407,12 @@ export class BackendInitializer { await lifecycleService.startup(); } catch (error: unknown) { assertError(error); - initLogger.onPluginFailed(pluginId, error); - throw error; + if (optionalBackends.includes(pluginId)) { + initLogger.onOptionalPluginFailed(pluginId, error); + } else { + initLogger.onPluginFailed(pluginId, error); + throw error; + } } }), ); diff --git a/packages/backend-app-api/src/wiring/createInitializationLogger.ts b/packages/backend-app-api/src/wiring/createInitializationLogger.ts index e623d43074..9aea45c33f 100644 --- a/packages/backend-app-api/src/wiring/createInitializationLogger.ts +++ b/packages/backend-app-api/src/wiring/createInitializationLogger.ts @@ -28,6 +28,7 @@ export function createInitializationLogger( ): { onPluginStarted(pluginId: string): void; onPluginFailed(pluginId: string, error: Error): void; + onOptionalPluginFailed(pluginId: string, error: Error): void; onAllStarted(): void; } { const logger = rootLogger?.child({ type: 'initialization' }); @@ -79,6 +80,13 @@ export function createInitializationLogger( error, ); }, + onOptionalPluginFailed(pluginId: string, error: Error) { + starting.delete(pluginId); + logger?.error( + `Plugin '${pluginId}' threw an error during startup, but the plugin is marked optional in config so startup will continue.`, + error, + ); + }, onAllStarted() { logger?.info(`Plugin initialization complete${getInitStatus()}`); diff --git a/packages/backend-app-api/src/wiring/index.ts b/packages/backend-app-api/src/wiring/index.ts index 00e4a9d4b4..0479ba32ef 100644 --- a/packages/backend-app-api/src/wiring/index.ts +++ b/packages/backend-app-api/src/wiring/index.ts @@ -14,5 +14,9 @@ * limitations under the License. */ -export type { Backend, CreateSpecializedBackendOptions } from './types'; +export type { + Backend, + BackendStartupOptions, + CreateSpecializedBackendOptions, +} from './types'; export { createSpecializedBackend } from './createSpecializedBackend'; diff --git a/packages/backend-app-api/src/wiring/types.ts b/packages/backend-app-api/src/wiring/types.ts index bd761ff3c8..0b19ec66ea 100644 --- a/packages/backend-app-api/src/wiring/types.ts +++ b/packages/backend-app-api/src/wiring/types.ts @@ -43,3 +43,18 @@ export interface CreateSpecializedBackendOptions { export type ServiceOrExtensionPoint = | ExtensionPoint | ServiceRef; + +/** + * @public + */ +export type BackendStartupOptions = { + [pluginId: string]: { + /** + * Used to mark plugins as optional, which allows the backend to start up even in the event + * of a plugin failure. Plugin failures without this configuration are fatal. This can + * enable leaving a crashing plugin installed, but still permit backend startup, which may + * help troubleshoot data-dependent issues. + */ + optional?: boolean; + }; +}; From c5b6f34ec78039a6726b6d5ac4edd344899a02da Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Fri, 7 Feb 2025 13:57:07 -0700 Subject: [PATCH 2/5] Add default startup options Signed-off-by: Tim Hansen --- .changeset/twenty-carrots-jog.md | 18 +++++- packages/backend-app-api/config.d.ts | 25 +++++++- .../src/wiring/BackendInitializer.test.ts | 61 ++++++++++++++++++- .../src/wiring/BackendInitializer.ts | 31 ++++++---- packages/backend-app-api/src/wiring/types.ts | 13 ++-- 5 files changed, 123 insertions(+), 25 deletions(-) diff --git a/.changeset/twenty-carrots-jog.md b/.changeset/twenty-carrots-jog.md index 9a87c403ff..71858ccd11 100644 --- a/.changeset/twenty-carrots-jog.md +++ b/.changeset/twenty-carrots-jog.md @@ -8,10 +8,24 @@ Added a configuration to permit backend plugin failures on startup: backend: ... startup: - plugin-x: - optional: true + plugins: + plugin-x: + optional: true ``` This configuration permits `plugin-x` to fail on startup. Omitting the `startup` configuration matches the previous behavior, wherein any individual plugin failure is fatal to backend startup. + +The default can also be changed, so that all plugins are considered optional +unless otherwise specified: + +```yaml +backend: + startup: + default: + optional: true + plugins: + catalog: + optional: false +``` diff --git a/packages/backend-app-api/config.d.ts b/packages/backend-app-api/config.d.ts index 7ffa882fc4..8af778ca42 100644 --- a/packages/backend-app-api/config.d.ts +++ b/packages/backend-app-api/config.d.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { BackendStartupOptions } from './src'; - export interface Config { backend?: { /** Used by the feature discovery service */ @@ -26,6 +24,27 @@ export interface Config { exclude?: string[]; }; - startup?: BackendStartupOptions; + startup?: { + default?: { + /** + * The default value for `optional` if not specified for a particular plugin. This defaults to + * false, which means `optional: true` must be specified for individual plugins to be considered + * optional. This can also be set to true, which flips the logic for individual plugins so that + * they must be set to `optional: false` to be required. + */ + optional?: boolean; + }; + plugins?: { + [pluginId: string]: { + /** + * Used to mark plugins as optional, which allows the backend to start up even in the event + * of a plugin failure. Plugin failures without this configuration are fatal. This can + * enable leaving a crashing plugin installed, but still permit backend startup, which may + * help troubleshoot data-dependent issues. + */ + optional?: boolean; + }; + }; + }; }; } diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts index 304b547d15..29fafa726d 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -565,7 +565,9 @@ describe('BackendInitializer', () => { mockServices.rootLifecycle.factory(), mockServices.rootLogger.factory(), mockServices.rootConfig.factory({ - data: { backend: { startup: { test: { optional: true } } } }, + data: { + backend: { startup: { plugins: { test: { optional: true } } } }, + }, }), ]); init.add( @@ -584,6 +586,63 @@ describe('BackendInitializer', () => { await init.start(); }); + it('should permit startup errors if the default is set', async () => { + const init = new BackendInitializer([ + mockServices.rootLifecycle.factory(), + mockServices.rootLogger.factory(), + mockServices.rootConfig.factory({ + data: { backend: { startup: { default: { optional: true } } } }, + }), + ]); + init.add( + createBackendPlugin({ + pluginId: 'test', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw new Error('NOPE'); + }, + }); + }, + }), + ); + await init.start(); + }); + + it('should forward errors for plugins explicitly marked as not optional when the default is true', async () => { + const init = new BackendInitializer([ + mockServices.rootLifecycle.factory(), + mockServices.rootLogger.factory(), + mockServices.rootConfig.factory({ + data: { + backend: { + startup: { + default: { optional: true }, + plugins: { test: { optional: false } }, + }, + }, + }, + }), + ]); + init.add( + createBackendPlugin({ + pluginId: 'test', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + throw new Error('NOPE'); + }, + }); + }, + }), + ); + await expect(init.start()).rejects.toThrow( + "Plugin 'test' startup failed; caused by Error: NOPE", + ); + }); + it('should forward errors when multiple plugins fail to start', async () => { const init = new BackendInitializer([]); init.add( diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index eb63ebe78b..5384423bfe 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -24,7 +24,8 @@ import { RootLifecycleService, createServiceFactory, } from '@backstage/backend-plugin-api'; -import { BackendStartupOptions, ServiceOrExtensionPoint } from './types'; +import { Config } from '@backstage/config'; +import { ServiceOrExtensionPoint } from './types'; // Direct internal import to avoid duplication // eslint-disable-next-line @backstage/no-relative-monorepo-imports import type { @@ -326,19 +327,14 @@ export class BackendInitializer { 'root', ); - const startupOptions = - rootConfig - ?.getOptionalConfig('backend.startup') - ?.get() ?? {}; - - // Gather backend pluginIds marked as optional, since these should not cause a startup failure - const optionalBackends = Object.entries(startupOptions) - .filter(([_, value]) => value?.optional) - .map(([key]) => key); - // All plugins are initialized in parallel const results = await Promise.allSettled( allPluginIds.map(async pluginId => { + const isPluginOptional = this.#getPluginOptionalityPredicate( + pluginId, + rootConfig, + ); + try { // Initialize all eager services await this.#serviceRegistry.initializeEagerServicesWithScope( @@ -407,7 +403,7 @@ export class BackendInitializer { await lifecycleService.startup(); } catch (error: unknown) { assertError(error); - if (optionalBackends.includes(pluginId)) { + if (isPluginOptional) { initLogger.onOptionalPluginFailed(pluginId, error); } else { initLogger.onPluginFailed(pluginId, error); @@ -639,6 +635,17 @@ export class BackendInitializer { } } } + + #getPluginOptionalityPredicate(pluginId: string, config?: Config): boolean { + const defaultStartupOptionalValue = + config?.getOptionalBoolean('backend.startup.default.optional') ?? false; + + return ( + config?.getOptionalBoolean( + `backend.startup.plugins.${pluginId}.optional`, + ) ?? defaultStartupOptionalValue + ); + } } function toInternalBackendFeature( diff --git a/packages/backend-app-api/src/wiring/types.ts b/packages/backend-app-api/src/wiring/types.ts index 0b19ec66ea..cea77268be 100644 --- a/packages/backend-app-api/src/wiring/types.ts +++ b/packages/backend-app-api/src/wiring/types.ts @@ -48,13 +48,12 @@ export type ServiceOrExtensionPoint = * @public */ export type BackendStartupOptions = { - [pluginId: string]: { - /** - * Used to mark plugins as optional, which allows the backend to start up even in the event - * of a plugin failure. Plugin failures without this configuration are fatal. This can - * enable leaving a crashing plugin installed, but still permit backend startup, which may - * help troubleshoot data-dependent issues. - */ + default?: { optional?: boolean; }; + plugins?: { + [pluginId: string]: { + optional?: boolean; + }; + }; }; From b088a541e0745fd9282840244ec2fc969184756c Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Fri, 7 Feb 2025 14:24:49 -0700 Subject: [PATCH 3/5] Remove extraneous type Signed-off-by: Tim Hansen --- packages/backend-app-api/src/wiring/index.ts | 6 +----- packages/backend-app-api/src/wiring/types.ts | 14 -------------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/packages/backend-app-api/src/wiring/index.ts b/packages/backend-app-api/src/wiring/index.ts index 0479ba32ef..00e4a9d4b4 100644 --- a/packages/backend-app-api/src/wiring/index.ts +++ b/packages/backend-app-api/src/wiring/index.ts @@ -14,9 +14,5 @@ * limitations under the License. */ -export type { - Backend, - BackendStartupOptions, - CreateSpecializedBackendOptions, -} from './types'; +export type { Backend, CreateSpecializedBackendOptions } from './types'; export { createSpecializedBackend } from './createSpecializedBackend'; diff --git a/packages/backend-app-api/src/wiring/types.ts b/packages/backend-app-api/src/wiring/types.ts index cea77268be..bd761ff3c8 100644 --- a/packages/backend-app-api/src/wiring/types.ts +++ b/packages/backend-app-api/src/wiring/types.ts @@ -43,17 +43,3 @@ export interface CreateSpecializedBackendOptions { export type ServiceOrExtensionPoint = | ExtensionPoint | ServiceRef; - -/** - * @public - */ -export type BackendStartupOptions = { - default?: { - optional?: boolean; - }; - plugins?: { - [pluginId: string]: { - optional?: boolean; - }; - }; -}; From 1d7a9277f4e9f4d50fd2df1b252b32dd4fd0f0b1 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Fri, 7 Feb 2025 15:18:35 -0700 Subject: [PATCH 4/5] Add expects to positive test cases Signed-off-by: Tim Hansen --- .../backend-app-api/src/wiring/BackendInitializer.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts index 29fafa726d..4edfc7275f 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -583,7 +583,7 @@ describe('BackendInitializer', () => { }, }), ); - await init.start(); + await expect(init.start()).resolves.not.toThrow(); }); it('should permit startup errors if the default is set', async () => { @@ -607,7 +607,7 @@ describe('BackendInitializer', () => { }, }), ); - await init.start(); + await expect(init.start()).resolves.not.toThrow(); }); it('should forward errors for plugins explicitly marked as not optional when the default is true', async () => { From 667fdb6766b9a9c0b6e09b42f0507b868d5a6a05 Mon Sep 17 00:00:00 2001 From: Tim Hansen Date: Thu, 13 Feb 2025 08:32:30 -0700 Subject: [PATCH 5/5] optional -> onPluginBootFailure Signed-off-by: Tim Hansen --- .changeset/twenty-carrots-jog.md | 14 +++++------ packages/backend-app-api/config.d.ts | 23 +++++++++-------- .../src/wiring/BackendInitializer.test.ts | 20 +++++++++------ .../src/wiring/BackendInitializer.ts | 25 +++++++++++-------- .../src/wiring/createInitializationLogger.ts | 6 ++--- 5 files changed, 50 insertions(+), 38 deletions(-) diff --git a/.changeset/twenty-carrots-jog.md b/.changeset/twenty-carrots-jog.md index 71858ccd11..f4b4db8fcb 100644 --- a/.changeset/twenty-carrots-jog.md +++ b/.changeset/twenty-carrots-jog.md @@ -10,22 +10,22 @@ backend: startup: plugins: plugin-x: - optional: true + onPluginBootFailure: continue ``` -This configuration permits `plugin-x` to fail on startup. Omitting the `startup` -configuration matches the previous behavior, wherein any individual plugin -failure is fatal to backend startup. +This configuration permits `plugin-x` to fail on startup. Omitting the +`onPluginBootFailure` configuration matches the previous behavior, wherein any +individual plugin failure aborts backend startup. -The default can also be changed, so that all plugins are considered optional +The default can also be changed, so that continuing on failure is the default unless otherwise specified: ```yaml backend: startup: default: - optional: true + onPluginBootFailure: continue plugins: catalog: - optional: false + onPluginBootFailure: abort ``` diff --git a/packages/backend-app-api/config.d.ts b/packages/backend-app-api/config.d.ts index 8af778ca42..0cf91852a1 100644 --- a/packages/backend-app-api/config.d.ts +++ b/packages/backend-app-api/config.d.ts @@ -27,22 +27,25 @@ export interface Config { startup?: { default?: { /** - * The default value for `optional` if not specified for a particular plugin. This defaults to - * false, which means `optional: true` must be specified for individual plugins to be considered - * optional. This can also be set to true, which flips the logic for individual plugins so that - * they must be set to `optional: false` to be required. + * The default value for `onPluginBootFailure` if not specified for a particular plugin. + * This defaults to 'abort', which means `onPluginBootFailure: continue` must be specified + * for backend startup to continue on plugin boot failure. This can also be set to + * 'continue', which flips the logic for individual plugins so that they must be set to + * `onPluginBootFailure: abort` to be required. */ - optional?: boolean; + onPluginBootFailure?: 'continue' | 'abort'; }; plugins?: { [pluginId: string]: { /** - * Used to mark plugins as optional, which allows the backend to start up even in the event - * of a plugin failure. Plugin failures without this configuration are fatal. This can - * enable leaving a crashing plugin installed, but still permit backend startup, which may - * help troubleshoot data-dependent issues. + * Used to control backend startup behavior when this plugin fails to boot up. Setting + * this to `continue` allows the backend to continue starting up, even if this plugin + * fails. This can enable leaving a crashing plugin installed, but still permit backend + * startup, which may help troubleshoot data-dependent issues. Plugin failures for plugins + * set to `abort` are fatal (this is the default unless overridden by the `default` + * setting). */ - optional?: boolean; + onPluginBootFailure?: 'continue' | 'abort'; }; }; }; diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts index 4edfc7275f..fe0a4f526b 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -560,13 +560,15 @@ describe('BackendInitializer', () => { ); }); - it('should permit startup errors for plugins marked as optional', async () => { + it('should permit startup errors for plugins with onPluginBootFailure: continue', async () => { const init = new BackendInitializer([ mockServices.rootLifecycle.factory(), mockServices.rootLogger.factory(), mockServices.rootConfig.factory({ data: { - backend: { startup: { plugins: { test: { optional: true } } } }, + backend: { + startup: { plugins: { test: { onPluginBootFailure: 'continue' } } }, + }, }, }), ]); @@ -586,12 +588,16 @@ describe('BackendInitializer', () => { await expect(init.start()).resolves.not.toThrow(); }); - it('should permit startup errors if the default is set', async () => { + it('should permit startup errors if the default onPluginBootFailure is continue', async () => { const init = new BackendInitializer([ mockServices.rootLifecycle.factory(), mockServices.rootLogger.factory(), mockServices.rootConfig.factory({ - data: { backend: { startup: { default: { optional: true } } } }, + data: { + backend: { + startup: { default: { onPluginBootFailure: 'continue' } }, + }, + }, }), ]); init.add( @@ -610,7 +616,7 @@ describe('BackendInitializer', () => { await expect(init.start()).resolves.not.toThrow(); }); - it('should forward errors for plugins explicitly marked as not optional when the default is true', async () => { + it('should forward errors for plugins explicitly marked to abort when the default is continue', async () => { const init = new BackendInitializer([ mockServices.rootLifecycle.factory(), mockServices.rootLogger.factory(), @@ -618,8 +624,8 @@ describe('BackendInitializer', () => { data: { backend: { startup: { - default: { optional: true }, - plugins: { test: { optional: false } }, + default: { onPluginBootFailure: 'continue' }, + plugins: { test: { onPluginBootFailure: 'abort' } }, }, }, }, diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index 5384423bfe..f521966d4d 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -330,7 +330,7 @@ export class BackendInitializer { // All plugins are initialized in parallel const results = await Promise.allSettled( allPluginIds.map(async pluginId => { - const isPluginOptional = this.#getPluginOptionalityPredicate( + const isBootFailurePermitted = this.#getPluginBootFailurePredicate( pluginId, rootConfig, ); @@ -403,8 +403,8 @@ export class BackendInitializer { await lifecycleService.startup(); } catch (error: unknown) { assertError(error); - if (isPluginOptional) { - initLogger.onOptionalPluginFailed(pluginId, error); + if (isBootFailurePermitted) { + initLogger.onPermittedPluginFailure(pluginId, error); } else { initLogger.onPluginFailed(pluginId, error); throw error; @@ -636,15 +636,18 @@ export class BackendInitializer { } } - #getPluginOptionalityPredicate(pluginId: string, config?: Config): boolean { - const defaultStartupOptionalValue = - config?.getOptionalBoolean('backend.startup.default.optional') ?? false; + #getPluginBootFailurePredicate(pluginId: string, config?: Config): boolean { + const defaultStartupBootFailureValue = + config?.getOptionalString( + 'backend.startup.default.onPluginBootFailure', + ) ?? 'abort'; - return ( - config?.getOptionalBoolean( - `backend.startup.plugins.${pluginId}.optional`, - ) ?? defaultStartupOptionalValue - ); + const pluginStartupBootFailureValue = + config?.getOptionalString( + `backend.startup.plugins.${pluginId}.onPluginBootFailure`, + ) ?? defaultStartupBootFailureValue; + + return pluginStartupBootFailureValue === 'continue'; } } diff --git a/packages/backend-app-api/src/wiring/createInitializationLogger.ts b/packages/backend-app-api/src/wiring/createInitializationLogger.ts index 9aea45c33f..0978cfb0c8 100644 --- a/packages/backend-app-api/src/wiring/createInitializationLogger.ts +++ b/packages/backend-app-api/src/wiring/createInitializationLogger.ts @@ -28,7 +28,7 @@ export function createInitializationLogger( ): { onPluginStarted(pluginId: string): void; onPluginFailed(pluginId: string, error: Error): void; - onOptionalPluginFailed(pluginId: string, error: Error): void; + onPermittedPluginFailure(pluginId: string, error: Error): void; onAllStarted(): void; } { const logger = rootLogger?.child({ type: 'initialization' }); @@ -80,10 +80,10 @@ export function createInitializationLogger( error, ); }, - onOptionalPluginFailed(pluginId: string, error: Error) { + onPermittedPluginFailure(pluginId: string, error: Error) { starting.delete(pluginId); logger?.error( - `Plugin '${pluginId}' threw an error during startup, but the plugin is marked optional in config so startup will continue.`, + `Plugin '${pluginId}' threw an error during startup, but boot failure is permitted for this plugin so startup will continue.`, error, ); },