diff --git a/.changeset/quick-jars-wait.md b/.changeset/quick-jars-wait.md new file mode 100644 index 0000000000..bbf6b42a89 --- /dev/null +++ b/.changeset/quick-jars-wait.md @@ -0,0 +1,26 @@ +--- +'@backstage/plugin-proxy-backend': patch +--- + +Adds a new option `skipInvalidTargets` for the proxy `createRouter` which allows the proxy backend to be started with an invalid proxy configuration. If configured, it will simply skip the failed proxy and mount the other valid proxies. + +To configure it to pass by failing proxies: + +``` +const router = await createRouter({ + config, + logger, + discovery, + skipInvalidProxies: true, +}); +``` + +If you would like it to fail if a proxy is configured badly: + +``` +const router = await createRouter({ + config, + logger, + discovery, +}); +``` diff --git a/plugins/proxy-backend/src/service/router.test.ts b/plugins/proxy-backend/src/service/router.test.ts index ddb524d3aa..3bfcc889f4 100644 --- a/plugins/proxy-backend/src/service/router.test.ts +++ b/plugins/proxy-backend/src/service/router.test.ts @@ -30,23 +30,96 @@ const mockCreateProxyMiddleware = createProxyMiddleware as jest.MockedFunction< >; describe('createRouter', () => { - it('works', async () => { - const logger = getVoidLogger(); - const config = new ConfigReader({ - backend: { - baseUrl: 'https://example.com:7007', - listen: { - port: 7007, + describe('where all proxy config are valid', () => { + it('works', async () => { + const logger = getVoidLogger(); + const config = new ConfigReader({ + backend: { + baseUrl: 'https://example.com:7007', + listen: { + port: 7007, + }, }, - }, + proxy: { + '/test': { + target: 'https://example.com', + headers: { + Authorization: 'Bearer supersecret', + }, + }, + }, + }); + const discovery = SingleHostDiscovery.fromConfig(config); + const router = await createRouter({ + config, + logger, + discovery, + }); + expect(router).toBeDefined(); }); - const discovery = SingleHostDiscovery.fromConfig(config); - const router = await createRouter({ - config, - logger, - discovery, + }); + + describe('where buildMiddleware would fail', () => { + it('throws an error if skip failures is not set', async () => { + const logger = getVoidLogger(); + logger.warn = jest.fn(); + const config = new ConfigReader({ + backend: { + baseUrl: 'https://example.com:7007', + listen: { + port: 7007, + }, + }, + // no target would cause the buildMiddleware to fail + proxy: { + '/test': { + headers: { + Authorization: 'Bearer supersecret', + }, + }, + }, + }); + const discovery = SingleHostDiscovery.fromConfig(config); + await expect( + createRouter({ + config, + logger, + discovery, + }), + ).rejects.toThrow(new Error('Proxy target must be a string')); + }); + + it('works if skip failures is set', async () => { + const logger = getVoidLogger(); + logger.warn = jest.fn(); + const config = new ConfigReader({ + backend: { + baseUrl: 'https://example.com:7007', + listen: { + port: 7007, + }, + }, + // no target would cause the buildMiddleware to fail + proxy: { + '/test': { + headers: { + Authorization: 'Bearer supersecret', + }, + }, + }, + }); + const discovery = SingleHostDiscovery.fromConfig(config); + const router = await createRouter({ + config, + logger, + discovery, + skipInvalidProxies: true, + }); + expect((logger.warn as jest.Mock).mock.calls[0][0]).toEqual( + 'skipped configuring /test due to Proxy target must be a string', + ); + expect(router).toBeDefined(); }); - expect(router).toBeDefined(); }); }); diff --git a/plugins/proxy-backend/src/service/router.ts b/plugins/proxy-backend/src/service/router.ts index c2123ae4b1..ca2c7870d8 100644 --- a/plugins/proxy-backend/src/service/router.ts +++ b/plugins/proxy-backend/src/service/router.ts @@ -51,6 +51,7 @@ export interface RouterOptions { logger: Logger; config: Config; discovery: PluginEndpointDiscovery; + skipInvalidProxies?: boolean; } export interface ProxyConfig extends Options { @@ -185,11 +186,20 @@ export async function createRouter( const { pathname: pathPrefix } = new URL(externalUrl); const proxyConfig = options.config.getOptional('proxy') ?? {}; + Object.entries(proxyConfig).forEach(([route, proxyRouteConfig]) => { - router.use( - route, - buildMiddleware(pathPrefix, options.logger, route, proxyRouteConfig), - ); + try { + router.use( + route, + buildMiddleware(pathPrefix, options.logger, route, proxyRouteConfig), + ); + } catch (e) { + if (options.skipInvalidProxies) { + options.logger.warn(`skipped configuring ${route} due to ${e.message}`); + } else { + throw e; + } + } }); return router;