From a4fa1ce09033eea3b2f74120706acd43a992f81d Mon Sep 17 00:00:00 2001 From: Crevil Date: Sat, 2 Jul 2022 09:24:33 +0200 Subject: [PATCH] Add config hot reloading to proxy-backend When working with the proxy configuration or plugins facilitating the proxy package the workflow is currently somewhat cumbersom as changes to the proxy configuration requires a full restart of the backend server. Use cases where the proxy configuration is updated is when testing out new routes for plugins to use and updating header configuration like authorization tokens. This change updates the proxy-backend to subscribe to configuration changes and update the router when changes to the proxy is made. Signed-off-by: Crevil --- .changeset/mean-adults-argue.md | 5 ++ .../proxy-backend/src/service/router.test.ts | 49 +++++++++++++++++++ plugins/proxy-backend/src/service/router.ts | 30 ++++++++++-- 3 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 .changeset/mean-adults-argue.md diff --git a/.changeset/mean-adults-argue.md b/.changeset/mean-adults-argue.md new file mode 100644 index 0000000000..2ba26f165d --- /dev/null +++ b/.changeset/mean-adults-argue.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-proxy-backend': minor +--- + +The proxy-backend now automatically reloads configuration when app-config.yaml is updated. diff --git a/plugins/proxy-backend/src/service/router.test.ts b/plugins/proxy-backend/src/service/router.test.ts index 3bfcc889f4..d3f7718c58 100644 --- a/plugins/proxy-backend/src/service/router.test.ts +++ b/plugins/proxy-backend/src/service/router.test.ts @@ -57,6 +57,55 @@ describe('createRouter', () => { }); expect(router).toBeDefined(); }); + + it('should be able to observe the config', async () => { + const logger = getVoidLogger(); + + // Grab the subscriber function and use mutable config data to mock a config file change + let subscriber: () => void; + const mutableConfigData: any = { + backend: { + baseUrl: 'https://example.com:7007', + listen: { + port: 7007, + }, + }, + proxy: { + '/test': { + target: 'https://example.com', + headers: { + Authorization: 'Bearer supersecret', + }, + }, + }, + }; + + const mockConfig = Object.assign(new ConfigReader(mutableConfigData), { + subscribe: (s: () => void) => { + subscriber = s; + return { unsubscribe: () => {} }; + }, + }); + + const discovery = SingleHostDiscovery.fromConfig(mockConfig); + const router = await createRouter({ + config: mockConfig, + logger, + discovery, + }); + expect(router).toBeDefined(); + + expect(router.stack[0].regexp).toEqual(/^\/test\/?(?=\/|$)/i); + expect(router.stack[1]).toBeUndefined(); + + mutableConfigData.proxy['/test2'] = { + target: 'https://example.com', + }; + subscriber!(); + + expect(router.stack[0].regexp).toEqual(/^\/test\/?(?=\/|$)/i); + expect(router.stack[1].regexp).toEqual(/^\/test2\/?(?=\/|$)/i); + }); }); describe('where buildMiddleware would fail', () => { diff --git a/plugins/proxy-backend/src/service/router.ts b/plugins/proxy-backend/src/service/router.ts index ca2c7870d8..3a6323eb2f 100644 --- a/plugins/proxy-backend/src/service/router.ts +++ b/plugins/proxy-backend/src/service/router.ts @@ -186,8 +186,34 @@ export async function createRouter( const { pathname: pathPrefix } = new URL(externalUrl); const proxyConfig = options.config.getOptional('proxy') ?? {}; + configureMiddlewares(options, router, pathPrefix, proxyConfig); - Object.entries(proxyConfig).forEach(([route, proxyRouteConfig]) => { + if (options.config.subscribe) { + let currentKey = JSON.stringify(proxyConfig); + + options.config.subscribe(() => { + const newProxyConfig = options.config.getOptional('proxy') ?? {}; + const newKey = JSON.stringify(newProxyConfig); + + if (currentKey !== newKey) { + currentKey = newKey; + + router.stack = []; + configureMiddlewares(options, router, pathPrefix, newProxyConfig); + } + }); + } + + return router; +} + +function configureMiddlewares( + options: RouterOptions, + router: express.Router, + pathPrefix: string, + proxyConfig: any, +) { + Object.entries(proxyConfig).forEach(([route, proxyRouteConfig]) => { try { router.use( route, @@ -201,6 +227,4 @@ export async function createRouter( } } }); - - return router; }