From 960c1cf13d2bc29e88738fc4c69bd40d9d5e3910 Mon Sep 17 00:00:00 2001 From: Jack Palmer Date: Wed, 29 Mar 2023 21:45:50 +0100 Subject: [PATCH] refactor: Rework config to be driven by target baseUrls Signed-off-by: Jack Palmer --- packages/backend-common/config.d.ts | 20 +++- .../src/discovery/HostDiscovery.test.ts | 109 +++++++++++++++++- .../src/discovery/HostDiscovery.ts | 33 +++++- 3 files changed, 151 insertions(+), 11 deletions(-) diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index 5be1a76b54..c1542c7131 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -203,13 +203,25 @@ export interface Config { * remove the default value that Backstage puts in place for that policy. */ csp?: { [policyId: string]: string[] | false }; + }; + /** Discovery options. */ + discovery?: { /** - * Discovery options. + * Endpoints * - * The keys are the plugin IDs, and the values are their endpoint URLs which - * will be used by `HostDiscovery` implementation. + * A list of target baseUrls and the associated plugins. */ - discovery?: { [pluginId: string]: string }; + endpoints: { + /** + * The target baseUrl to use for the plugin + * + * Can be either a string or an object with internal and external keys. + * Targets with `{pluginId}` in the url will be replaced with the pluginId. + */ + target: string | { internal: string; external: string }; + /** Array of plugins which use the target baseUrl. */ + plugins: string[]; + }[]; }; } diff --git a/packages/backend-common/src/discovery/HostDiscovery.test.ts b/packages/backend-common/src/discovery/HostDiscovery.test.ts index b07716352e..a288764b4c 100644 --- a/packages/backend-common/src/discovery/HostDiscovery.test.ts +++ b/packages/backend-common/src/discovery/HostDiscovery.test.ts @@ -82,7 +82,7 @@ describe('HostDiscovery', () => { ); }); - it('uses plugin specific baseUrl from config if provided', async () => { + it('uses plugin specific targets from config if provided', async () => { const discovery = HostDiscovery.fromConfig( new ConfigReader({ backend: { @@ -90,7 +90,41 @@ describe('HostDiscovery', () => { listen: { port: 80, host: 'localhost' }, }, discovery: { - catalog: 'http://catalog-backend:8080/api/catalog', + endpoints: [ + { + target: { + internal: 'http://catalog-backend-internal:8080/api/catalog', + external: 'http://catalog-backend-external:8080/api/catalog', + }, + plugins: ['catalog'], + }, + ], + }, + }), + ); + + await expect(discovery.getBaseUrl('catalog')).resolves.toBe( + 'http://catalog-backend-internal:8080/api/catalog', + ); + await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( + 'http://catalog-backend-external:8080/api/catalog', + ); + }); + + it('uses a single target for internal and external for a plugin', async () => { + const discovery = HostDiscovery.fromConfig( + new ConfigReader({ + backend: { + baseUrl: 'http://localhost:40', + listen: { port: 80, host: 'localhost' }, + }, + discovery: { + endpoints: [ + { + target: 'http://catalog-backend:8080/api/catalog', + plugins: ['catalog'], + }, + ], }, }), ); @@ -102,4 +136,75 @@ describe('HostDiscovery', () => { 'http://catalog-backend:8080/api/catalog', ); }); + + it('defaults to the backend baseUrl when there is not an endpoint for a plugin', async () => { + const discovery = HostDiscovery.fromConfig( + new ConfigReader({ + backend: { + baseUrl: 'http://localhost:40', + listen: { port: 80, host: 'localhost' }, + }, + discovery: { + endpoints: [ + { + target: 'http://catalog-backend:8080/api/catalog', + plugins: ['catalog'], + }, + ], + }, + }), + ); + + await expect(discovery.getBaseUrl('scaffolder')).resolves.toBe( + 'http://localhost:80/api/scaffolder', + ); + await expect(discovery.getExternalBaseUrl('scaffolder')).resolves.toBe( + 'http://localhost:40/api/scaffolder', + ); + }); + + it('replaces {pluginId} in the target', async () => { + const discovery = HostDiscovery.fromConfig( + new ConfigReader({ + backend: { + baseUrl: 'http://localhost:40', + listen: { port: 80, host: 'localhost' }, + }, + discovery: { + endpoints: [ + { + target: 'http://common-backend:8080/api/{pluginId}', + plugins: ['catalog', 'docs'], + }, + { + target: { + internal: 'http://scaffolder-internal:8080/api/{pluginId}', + external: 'http://scaffolder-external:8080/api/{pluginId}', + }, + plugins: ['scaffolder'], + }, + ], + }, + }), + ); + + await expect(discovery.getBaseUrl('catalog')).resolves.toBe( + 'http://common-backend:8080/api/catalog', + ); + await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( + 'http://common-backend:8080/api/catalog', + ); + await expect(discovery.getBaseUrl('docs')).resolves.toBe( + 'http://common-backend:8080/api/docs', + ); + await expect(discovery.getExternalBaseUrl('docs')).resolves.toBe( + 'http://common-backend:8080/api/docs', + ); + await expect(discovery.getBaseUrl('scaffolder')).resolves.toBe( + 'http://scaffolder-internal:8080/api/scaffolder', + ); + await expect(discovery.getExternalBaseUrl('scaffolder')).resolves.toBe( + 'http://scaffolder-external:8080/api/scaffolder', + ); + }); }); diff --git a/packages/backend-common/src/discovery/HostDiscovery.ts b/packages/backend-common/src/discovery/HostDiscovery.ts index d9f507139d..e8af78b064 100644 --- a/packages/backend-common/src/discovery/HostDiscovery.ts +++ b/packages/backend-common/src/discovery/HostDiscovery.ts @@ -18,6 +18,8 @@ import { Config } from '@backstage/config'; import { PluginEndpointDiscovery } from './types'; import { readHttpServerOptions } from '@backstage/backend-app-api'; +type Target = string | { internal: string; external: string }; + /** * HostDiscovery is a basic PluginEndpointDiscovery implementation * that assumes that all plugins are hosted in a single deployment. @@ -28,7 +30,6 @@ import { readHttpServerOptions } from '@backstage/backend-app-api'; * * @public */ - export class HostDiscovery implements PluginEndpointDiscovery { /** * Creates a new HostDiscovery discovery instance by reading @@ -79,17 +80,39 @@ export class HostDiscovery implements PluginEndpointDiscovery { private readonly discoveryConfig: Config | undefined, ) {} + private getTargetFromConfig(pluginId: string, type: 'internal' | 'external') { + const endpoints = this.discoveryConfig?.getOptionalConfigArray('endpoints'); + + const target = endpoints + ?.find(endpoint => endpoint.getStringArray('plugins').includes(pluginId)) + ?.get('target'); + + if (!target) { + return null; + } + + if (typeof target === 'string') { + return target.replace('{pluginId}', pluginId); + } + + return target[type].replace('{pluginId}', pluginId); + } + async getBaseUrl(pluginId: string): Promise { - if (this.discoveryConfig?.has(pluginId)) { - return this.discoveryConfig.getString(pluginId); + const target = this.getTargetFromConfig(pluginId, 'internal'); + + if (target) { + return target; } return `${this.internalBaseUrl}/${pluginId}`; } async getExternalBaseUrl(pluginId: string): Promise { - if (this.discoveryConfig?.has(pluginId)) { - return this.discoveryConfig.getString(pluginId); + const target = this.getTargetFromConfig(pluginId, 'external'); + + if (target) { + return target; } return `${this.externalBaseUrl}/${pluginId}`;