refactor: Rework config to be driven by target baseUrls

Signed-off-by: Jack Palmer <jackpalmer@spotify.com>
This commit is contained in:
Jack Palmer
2023-03-29 21:45:50 +01:00
parent b6d873c3ab
commit 960c1cf13d
3 changed files with 151 additions and 11 deletions
+16 -4
View File
@@ -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[];
}[];
};
}
@@ -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',
);
});
});
@@ -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>('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<string> {
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<string> {
if (this.discoveryConfig?.has(pluginId)) {
return this.discoveryConfig.getString(pluginId);
const target = this.getTargetFromConfig(pluginId, 'external');
if (target) {
return target;
}
return `${this.externalBaseUrl}/${pluginId}`;