From 51329677e184472f045d184a31c16244cf425e15 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 25 Apr 2023 16:08:14 +0200 Subject: [PATCH] core-app-api: refactor into FrontendHostDiscovery Signed-off-by: Patrik Oldsberg --- .changeset/quick-flies-guess.md | 2 +- .changeset/soft-readers-exist.md | 2 +- packages/core-app-api/api-report.md | 24 +++--- ....test.ts => FrontendHostDiscovery.test.ts} | 20 ++--- .../DiscoveryApi/FrontendHostDiscovery.ts | 83 ++++++++++++++++++ .../DiscoveryApi/HostDiscovery.ts | 86 ------------------- .../DiscoveryApi/UrlPatternDiscovery.ts | 2 +- .../implementations/DiscoveryApi/index.ts | 2 +- 8 files changed, 109 insertions(+), 112 deletions(-) rename packages/core-app-api/src/apis/implementations/DiscoveryApi/{HostDiscovery.test.ts => FrontendHostDiscovery.test.ts} (89%) create mode 100644 packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.ts delete mode 100644 packages/core-app-api/src/apis/implementations/DiscoveryApi/HostDiscovery.ts diff --git a/.changeset/quick-flies-guess.md b/.changeset/quick-flies-guess.md index 8e6a7ead40..73e5589b98 100644 --- a/.changeset/quick-flies-guess.md +++ b/.changeset/quick-flies-guess.md @@ -2,4 +2,4 @@ '@backstage/core-app-api': patch --- -Added HostDiscovery for config driven discovery implementation +Added `FrontendHostDiscovery` for config driven discovery implementation diff --git a/.changeset/soft-readers-exist.md b/.changeset/soft-readers-exist.md index bdaa06e476..8fce13caca 100644 --- a/.changeset/soft-readers-exist.md +++ b/.changeset/soft-readers-exist.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -Added HostDiscovery to supersede deprecated SingleHostDiscovery (deprecated due to name) +Added `HostDiscovery` to supersede deprecated `SingleHostDiscovery` (deprecated due to name) diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index ed72270841..514a58d289 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -410,6 +410,18 @@ export type FlatRoutesProps = { children: ReactNode; }; +// @public +export class FrontendHostDiscovery implements DiscoveryApi { + static fromConfig( + config: Config, + options?: { + pathPattern?: string; + }, + ): FrontendHostDiscovery; + // (undocumented) + getBaseUrl(pluginId: string): Promise; +} + // @public export class GithubAuth { // (undocumented) @@ -428,18 +440,6 @@ export class GoogleAuth { static create(options: OAuthApiCreateOptions): typeof googleAuthApiRef.T; } -// @public -export class HostDiscovery implements DiscoveryApi { - static fromConfig( - config: Config, - options?: { - basePath?: string; - }, - ): HostDiscovery; - // (undocumented) - getBaseUrl(pluginId: string): Promise; -} - // @public export class LocalStorageFeatureFlags implements FeatureFlagsApi { // (undocumented) diff --git a/packages/core-app-api/src/apis/implementations/DiscoveryApi/HostDiscovery.test.ts b/packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.test.ts similarity index 89% rename from packages/core-app-api/src/apis/implementations/DiscoveryApi/HostDiscovery.test.ts rename to packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.test.ts index 942a1861f9..0ba6457a1b 100644 --- a/packages/core-app-api/src/apis/implementations/DiscoveryApi/HostDiscovery.test.ts +++ b/packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.test.ts @@ -15,11 +15,11 @@ */ import { ConfigReader } from '@backstage/config'; -import { HostDiscovery } from './HostDiscovery'; +import { FrontendHostDiscovery } from './FrontendHostDiscovery'; -describe('HostDiscovery', () => { +describe('FrontendHostDiscovery', () => { it('is created from config', async () => { - const discovery = HostDiscovery.fromConfig( + const discovery = FrontendHostDiscovery.fromConfig( new ConfigReader({ backend: { baseUrl: 'http://localhost:40', @@ -33,13 +33,13 @@ describe('HostDiscovery', () => { }); it('can configure the base path', async () => { - const discovery = HostDiscovery.fromConfig( + const discovery = FrontendHostDiscovery.fromConfig( new ConfigReader({ backend: { baseUrl: 'http://localhost:40', }, }), - { basePath: '/service' }, + { pathPattern: '/service/{{pluginId}}' }, ); await expect(discovery.getBaseUrl('catalog')).resolves.toBe( @@ -48,7 +48,7 @@ describe('HostDiscovery', () => { }); it('uses plugin specific targets from config if provided', async () => { - const discovery = HostDiscovery.fromConfig( + const discovery = FrontendHostDiscovery.fromConfig( new ConfigReader({ backend: { baseUrl: 'http://localhost:40', @@ -73,7 +73,7 @@ describe('HostDiscovery', () => { }); it('uses a single target for internal and external for a plugin', async () => { - const discovery = HostDiscovery.fromConfig( + const discovery = FrontendHostDiscovery.fromConfig( new ConfigReader({ backend: { baseUrl: 'http://localhost:40', @@ -95,7 +95,7 @@ describe('HostDiscovery', () => { }); it('defaults to the backend baseUrl when there is not an endpoint for a plugin', async () => { - const discovery = HostDiscovery.fromConfig( + const discovery = FrontendHostDiscovery.fromConfig( new ConfigReader({ backend: { baseUrl: 'http://localhost:40', @@ -117,7 +117,7 @@ describe('HostDiscovery', () => { }); it('replaces {{pluginId}} or {{ pluginId }} in the target', async () => { - const discovery = HostDiscovery.fromConfig( + const discovery = FrontendHostDiscovery.fromConfig( new ConfigReader({ backend: { baseUrl: 'http://localhost:40', @@ -152,7 +152,7 @@ describe('HostDiscovery', () => { }); it('encodes the pluginId', async () => { - const discovery = HostDiscovery.fromConfig( + const discovery = FrontendHostDiscovery.fromConfig( new ConfigReader({ backend: { baseUrl: 'http://localhost:40', diff --git a/packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.ts b/packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.ts new file mode 100644 index 0000000000..950b746684 --- /dev/null +++ b/packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.ts @@ -0,0 +1,83 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Config } from '@backstage/config'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; +import { UrlPatternDiscovery } from './UrlPatternDiscovery'; + +/** + * FrontendHostDiscovery is a config driven DiscoveryApi implementation. + * It uses the app-config to determine the url for a plugin. + * + * @public + */ +export class FrontendHostDiscovery implements DiscoveryApi { + /** + * Creates a new FrontendHostDiscovery discovery instance by reading + * the external target URL from the `discovery.endpoints` config section. + * + * eg. + * ```yaml + * discovery: + * endpoints: + * - target: https://internal.example.com/internal-catalog + * plugins: [catalog] + * - target: https://internal.example.com/secure/api/{{pluginId}} + * plugins: [auth, permissions] + * - target: + * internal: https://internal.example.com/search + * external: https://example.com/search + * plugins: [search] + * ``` + * + * If a plugin is not declared in the config, the discovery will fall back to using the baseUrl with + * the provided `pathPattern` appended. The default path pattern is `"/api/{{ pluginId }}"`. + */ + static fromConfig(config: Config, options?: { pathPattern?: string }) { + const path = options?.pathPattern ?? '/api/{{ pluginId }}'; + const baseUrl = config.getString('backend.baseUrl'); + + const endpoints = config + .getOptionalConfigArray('discovery.endpoints') + ?.flatMap(e => { + const target = + typeof e.get('target') === 'object' + ? e.getString('target.external') + : e.getString('target'); + const discovery = UrlPatternDiscovery.compile(target); + return e + .getStringArray('plugins') + .map(pluginId => [pluginId, discovery] as const); + }); + + return new FrontendHostDiscovery( + new Map(endpoints), + UrlPatternDiscovery.compile(`${baseUrl}${path}`), + ); + } + + private constructor( + private readonly endpoints: Map, + private readonly defaultEndpoint: DiscoveryApi, + ) {} + + async getBaseUrl(pluginId: string): Promise { + const endpoint = this.endpoints.get(pluginId); + if (endpoint) { + return endpoint.getBaseUrl(pluginId); + } + return this.defaultEndpoint.getBaseUrl(pluginId); + } +} diff --git a/packages/core-app-api/src/apis/implementations/DiscoveryApi/HostDiscovery.ts b/packages/core-app-api/src/apis/implementations/DiscoveryApi/HostDiscovery.ts deleted file mode 100644 index b2f403cf76..0000000000 --- a/packages/core-app-api/src/apis/implementations/DiscoveryApi/HostDiscovery.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Config } from '@backstage/config'; -import { DiscoveryApi } from '@backstage/core-plugin-api'; - -type Target = string | { internal: string; external: string }; - -/** - * HostDiscovery is a config driven DiscoveryApi implementation. - * It uses the app-config to determine the url for a plugin. - * - * @public - */ -export class HostDiscovery implements DiscoveryApi { - /** - * Creates a new HostDiscovery discovery instance by reading - * from the `discovery.endpoints` config section. If both internal and external exist, then external is used. - * - * eg. - * ```yaml - * discovery: - * endpoints: - * - target: https://internal.example.com/internal-catalog - * plugins: [catalog] - * - target: https://internal.example.com/secure/api/{{pluginId}} - * plugins: [auth, permissions] - * - target: - * internal: https://internal.example.com/search - * external: https://example.com/search - * plugins: [search] - * ``` - * - * If a plugin is not declared in the config, the baseUrl is used, defaulting to "/api" unless `basePath` is provided in the options. - */ - static fromConfig(config: Config, options?: { basePath?: string }) { - const basePath = options?.basePath ?? '/api'; - const baseUrl = config.getString('backend.baseUrl'); - - return new HostDiscovery( - baseUrl + basePath, - config.getOptionalConfig('discovery'), - ); - } - - private constructor( - private readonly baseUrl: string, - private readonly discoveryConfig: Config | undefined, - ) {} - - async getBaseUrl(pluginId: string): Promise { - const endpoints = this.discoveryConfig?.getOptionalConfigArray('endpoints'); - - const target = endpoints - ?.find(endpoint => endpoint.getStringArray('plugins').includes(pluginId)) - ?.get('target'); - - if (!target) { - return `${this.baseUrl}/${encodeURIComponent(pluginId)}`; - } - - if (typeof target === 'string') { - return target.replace( - /\{\{\s*pluginId\s*\}\}/g, - encodeURIComponent(pluginId), - ); - } - - return target.external.replace( - /\{\{\s*pluginId\s*\}\}/g, - encodeURIComponent(pluginId), - ); - } -} diff --git a/packages/core-app-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts b/packages/core-app-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts index cba8344977..58ffc9441b 100644 --- a/packages/core-app-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts +++ b/packages/core-app-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts @@ -58,6 +58,6 @@ export class UrlPatternDiscovery implements DiscoveryApi { private constructor(private readonly parts: string[]) {} async getBaseUrl(pluginId: string): Promise { - return this.parts.join(pluginId); + return this.parts.join(encodeURIComponent(pluginId)); } } diff --git a/packages/core-app-api/src/apis/implementations/DiscoveryApi/index.ts b/packages/core-app-api/src/apis/implementations/DiscoveryApi/index.ts index be4b3a821c..6972ca7b85 100644 --- a/packages/core-app-api/src/apis/implementations/DiscoveryApi/index.ts +++ b/packages/core-app-api/src/apis/implementations/DiscoveryApi/index.ts @@ -15,4 +15,4 @@ */ export { UrlPatternDiscovery } from './UrlPatternDiscovery'; -export { HostDiscovery } from './HostDiscovery'; +export { FrontendHostDiscovery } from './FrontendHostDiscovery';