backend-common: move HostDiscovery to backend-app-api

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-09-29 17:45:37 +02:00
parent a1ac0ed0fa
commit 74491c9602
16 changed files with 217 additions and 161 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-common': patch
---
The `HostDiscovery` export has been deprecated, import it from `@backstage/backend-app-api` instead.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-test-utils': patch
---
Updated to import `HostDiscovery` from `@backstage/backend-app-api`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-app-api': patch
---
Moved `HostDiscovery` from `@backstage/backend-common`.
+16 -2
View File
@@ -10,6 +10,7 @@ import { BackendFeature } from '@backstage/backend-plugin-api';
import { CacheClient } from '@backstage/backend-common';
import { Config } from '@backstage/config';
import { CorsOptions } from 'cors';
import { DiscoveryService } from '@backstage/backend-plugin-api';
import { ErrorRequestHandler } from 'express';
import { Express as Express_2 } from 'express';
import { Format } from 'logform';
@@ -25,7 +26,6 @@ import { LoadConfigOptionsRemote } from '@backstage/config-loader';
import { LoggerService } from '@backstage/backend-plugin-api';
import { PermissionsService } from '@backstage/backend-plugin-api';
import { PluginDatabaseManager } from '@backstage/backend-common';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { RemoteConfigSourceOptions } from '@backstage/config-loader';
import { RequestHandler } from 'express';
import { RequestListener } from 'http';
@@ -114,7 +114,7 @@ export interface DefaultRootHttpRouterOptions {
// @public (undocumented)
export const discoveryServiceFactory: () => ServiceFactory<
PluginEndpointDiscovery,
DiscoveryService,
'plugin'
>;
@@ -128,6 +128,20 @@ export interface ExtendedHttpServer extends http.Server {
stop(): Promise<void>;
}
// @public
export class HostDiscovery implements DiscoveryService {
static fromConfig(
config: Config,
options?: {
basePath?: string;
},
): HostDiscovery;
// (undocumented)
getBaseUrl(pluginId: string): Promise<string>;
// (undocumented)
getExternalBaseUrl(pluginId: string): Promise<string>;
}
// @public (undocumented)
export interface HttpRouterFactoryOptions {
getPath?(pluginId: string): string;
+37
View File
@@ -0,0 +1,37 @@
/*
* Copyright 2020 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.
*/
export interface Config {
/** Discovery options. */
discovery?: {
/**
* Endpoints
*
* A list of target baseUrls and the associated plugins.
*/
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}}` or `{{ 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[];
}[];
};
}
+2
View File
@@ -90,8 +90,10 @@
"mock-fs": "^5.2.0",
"supertest": "^6.1.3"
},
"configSchema": "config.d.ts",
"files": [
"dist",
"config.d.ts",
"alpha"
]
}
@@ -0,0 +1,132 @@
/*
* Copyright 2020 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 { readHttpServerOptions } from '@backstage/backend-app-api';
import { DiscoveryService } from '@backstage/backend-plugin-api';
type Target = string | { internal: string; external: string };
/**
* HostDiscovery is a basic PluginEndpointDiscovery implementation
* that can handle plugins that are hosted in a single or multiple deployments.
*
* The deployment may be scaled horizontally, as long as the external URL
* is the same for all instances. However, internal URLs will always be
* resolved to the same host, so there won't be any balancing of internal traffic.
*
* @public
*/
export class HostDiscovery implements DiscoveryService {
/**
* Creates a new HostDiscovery discovery instance by reading
* from the `backend` config section, specifically the `.baseUrl` for
* discovering the external URL, and the `.listen` and `.https` config
* for the internal one.
*
* Can be overridden in config by providing a target and corresponding plugins in `discovery.endpoints`.
* eg.
* ```yaml
* discovery:
* endpoints:
* - target: https://internal.example.com/internal-catalog
* plugins: [catalog]
* - target: https://internal.example.com/secure/api/{{pluginId}}
* plugins: [auth, permission]
* - target:
* internal: https://internal.example.com/search
* external: https://example.com/search
* plugins: [search]
* ```
*
* The basePath defaults to `/api`, meaning the default full internal
* path for the `catalog` plugin will be `http://localhost:7007/api/catalog`.
*/
static fromConfig(config: Config, options?: { basePath?: string }) {
const basePath = options?.basePath ?? '/api';
const externalBaseUrl = config
.getString('backend.baseUrl')
.replace(/\/+$/, '');
const {
listen: { host: listenHost = '::', port: listenPort },
} = readHttpServerOptions(config.getConfig('backend'));
const protocol = config.has('backend.https') ? 'https' : 'http';
// Translate bind-all to localhost, and support IPv6
let host = listenHost;
if (host === '::' || host === '') {
// We use localhost instead of ::1, since IPv6-compatible systems should default
// to using IPv6 when they see localhost, but if the system doesn't support IPv6
// things will still work.
host = 'localhost';
} else if (host === '0.0.0.0') {
host = '127.0.0.1';
}
if (host.includes(':')) {
host = `[${host}]`;
}
const internalBaseUrl = `${protocol}://${host}:${listenPort}`;
return new HostDiscovery(
internalBaseUrl + basePath,
externalBaseUrl + basePath,
config.getOptionalConfig('discovery'),
);
}
private constructor(
private readonly internalBaseUrl: string,
private readonly externalBaseUrl: string,
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) {
const baseUrl =
type === 'external' ? this.externalBaseUrl : this.internalBaseUrl;
return `${baseUrl}/${encodeURIComponent(pluginId)}`;
}
if (typeof target === 'string') {
return target.replace(
/\{\{\s*pluginId\s*\}\}/g,
encodeURIComponent(pluginId),
);
}
return target[type].replace(
/\{\{\s*pluginId\s*\}\}/g,
encodeURIComponent(pluginId),
);
}
async getBaseUrl(pluginId: string): Promise<string> {
return this.getTargetFromConfig(pluginId, 'internal');
}
async getExternalBaseUrl(pluginId: string): Promise<string> {
return this.getTargetFromConfig(pluginId, 'external');
}
}
@@ -14,11 +14,11 @@
* limitations under the License.
*/
import { HostDiscovery } from '@backstage/backend-common';
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { HostDiscovery } from './HostDiscovery';
/** @public */
export const discoveryServiceFactory = createServiceFactory({
@@ -15,3 +15,4 @@
*/
export { discoveryServiceFactory } from './discoveryServiceFactory';
export { HostDiscovery } from './HostDiscovery';
+3 -13
View File
@@ -28,6 +28,7 @@ import { GiteaIntegration } from '@backstage/integration';
import { GithubCredentialsProvider } from '@backstage/integration';
import { GithubIntegration } from '@backstage/integration';
import { GitLabIntegration } from '@backstage/integration';
import { HostDiscovery as HostDiscovery_2 } from '@backstage/backend-app-api';
import { IdentityService } from '@backstage/backend-plugin-api';
import { isChildPath } from '@backstage/cli-common';
import { Knex } from 'knex';
@@ -475,18 +476,7 @@ export class GitlabUrlReader implements UrlReader {
}
// @public
export class HostDiscovery implements PluginEndpointDiscovery {
static fromConfig(
config: Config,
options?: {
basePath?: string;
},
): HostDiscovery;
// (undocumented)
getBaseUrl(pluginId: string): Promise<string>;
// (undocumented)
getExternalBaseUrl(pluginId: string): Promise<string>;
}
export const HostDiscovery: typeof HostDiscovery_2;
export { isChildPath };
@@ -747,7 +737,7 @@ export type ServiceBuilder = {
export function setRootLogger(newLogger: winston.Logger): void;
// @public @deprecated
export const SingleHostDiscovery: typeof HostDiscovery;
export const SingleHostDiscovery: typeof HostDiscovery_2;
// @public
export type StatusCheck = () => Promise<any>;
-20
View File
@@ -216,24 +216,4 @@ export interface Config {
*/
csp?: { [policyId: string]: string[] | false };
};
/** Discovery options. */
discovery?: {
/**
* Endpoints
*
* A list of target baseUrls and the associated plugins.
*/
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}}` or `{{ 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[];
}[];
};
}
@@ -14,11 +14,9 @@
* limitations under the License.
*/
import { Config } from '@backstage/config';
import { PluginEndpointDiscovery } from './types';
import { readHttpServerOptions } from '@backstage/backend-app-api';
import { HostDiscovery as _HostDiscovery } from '@backstage/backend-app-api';
type Target = string | { internal: string; external: string };
export type { DiscoveryService as PluginEndpointDiscovery } from '@backstage/backend-plugin-api';
/**
* HostDiscovery is a basic PluginEndpointDiscovery implementation
@@ -30,106 +28,7 @@ type Target = string | { internal: string; external: string };
*
* @public
*/
export class HostDiscovery implements PluginEndpointDiscovery {
/**
* Creates a new HostDiscovery discovery instance by reading
* from the `backend` config section, specifically the `.baseUrl` for
* discovering the external URL, and the `.listen` and `.https` config
* for the internal one.
*
* Can be overridden in config by providing a target and corresponding plugins in `discovery.endpoints`.
* eg.
* ```yaml
* discovery:
* endpoints:
* - target: https://internal.example.com/internal-catalog
* plugins: [catalog]
* - target: https://internal.example.com/secure/api/{{pluginId}}
* plugins: [auth, permission]
* - target:
* internal: https://internal.example.com/search
* external: https://example.com/search
* plugins: [search]
* ```
*
* The basePath defaults to `/api`, meaning the default full internal
* path for the `catalog` plugin will be `http://localhost:7007/api/catalog`.
*/
static fromConfig(config: Config, options?: { basePath?: string }) {
const basePath = options?.basePath ?? '/api';
const externalBaseUrl = config
.getString('backend.baseUrl')
.replace(/\/+$/, '');
const {
listen: { host: listenHost = '::', port: listenPort },
} = readHttpServerOptions(config.getConfig('backend'));
const protocol = config.has('backend.https') ? 'https' : 'http';
// Translate bind-all to localhost, and support IPv6
let host = listenHost;
if (host === '::' || host === '') {
// We use localhost instead of ::1, since IPv6-compatible systems should default
// to using IPv6 when they see localhost, but if the system doesn't support IPv6
// things will still work.
host = 'localhost';
} else if (host === '0.0.0.0') {
host = '127.0.0.1';
}
if (host.includes(':')) {
host = `[${host}]`;
}
const internalBaseUrl = `${protocol}://${host}:${listenPort}`;
return new HostDiscovery(
internalBaseUrl + basePath,
externalBaseUrl + basePath,
config.getOptionalConfig('discovery'),
);
}
private constructor(
private readonly internalBaseUrl: string,
private readonly externalBaseUrl: string,
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) {
const baseUrl =
type === 'external' ? this.externalBaseUrl : this.internalBaseUrl;
return `${baseUrl}/${encodeURIComponent(pluginId)}`;
}
if (typeof target === 'string') {
return target.replace(
/\{\{\s*pluginId\s*\}\}/g,
encodeURIComponent(pluginId),
);
}
return target[type].replace(
/\{\{\s*pluginId\s*\}\}/g,
encodeURIComponent(pluginId),
);
}
async getBaseUrl(pluginId: string): Promise<string> {
return this.getTargetFromConfig(pluginId, 'internal');
}
async getExternalBaseUrl(pluginId: string): Promise<string> {
return this.getTargetFromConfig(pluginId, 'external');
}
}
export const HostDiscovery = _HostDiscovery;
/**
* SingleHostDiscovery is a basic PluginEndpointDiscovery implementation
@@ -142,4 +41,4 @@ export class HostDiscovery implements PluginEndpointDiscovery {
* @public
* @deprecated Use {@link HostDiscovery} instead
*/
export const SingleHostDiscovery = HostDiscovery;
export const SingleHostDiscovery = _HostDiscovery;
@@ -13,5 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { HostDiscovery, SingleHostDiscovery } from './HostDiscovery';
export type { PluginEndpointDiscovery } from './types';
export {
HostDiscovery,
SingleHostDiscovery,
type PluginEndpointDiscovery,
} from './HostDiscovery';
@@ -1,17 +0,0 @@
/*
* Copyright 2020 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.
*/
export type { DiscoveryService as PluginEndpointDiscovery } from '@backstage/backend-plugin-api';
@@ -20,9 +20,9 @@ import {
MiddlewareFactory,
createHttpServer,
ExtendedHttpServer,
HostDiscovery,
DefaultRootHttpRouter,
} from '@backstage/backend-app-api';
import { HostDiscovery } from '@backstage/backend-common';
import {
createServiceFactory,
BackendFeature,