backend-app-api: Remove deprecated service implementations

Co-authored-by: Camila Belo <camilaibs@gmail.com>
Signed-off-by: Johan Haals <johan.haals@gmail.com>
This commit is contained in:
Johan Haals
2024-08-13 16:03:41 +02:00
parent 9ad63932cf
commit 24bed62544
49 changed files with 14 additions and 1951 deletions
-207
View File
@@ -6,52 +6,25 @@
/// <reference types="node" />
import type { AppConfig } from '@backstage/config';
import { AuthService } from '@backstage/backend-plugin-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
import { CacheService } from '@backstage/backend-plugin-api';
import { Config } from '@backstage/config';
import { ConfigSchema } from '@backstage/config-loader';
import { CorsOptions } from 'cors';
import { DatabaseService } from '@backstage/backend-plugin-api';
import { DiscoveryService } from '@backstage/backend-plugin-api';
import { ErrorRequestHandler } from 'express';
import { Express as Express_2 } from 'express';
import { Format } from 'logform';
import { Handler } from 'express';
import { HelmetOptions } from 'helmet';
import * as http from 'http';
import { HttpAuthService } from '@backstage/backend-plugin-api';
import { HttpRouterService } from '@backstage/backend-plugin-api';
import { HumanDuration } from '@backstage/types';
import { IdentityService } from '@backstage/backend-plugin-api';
import { JsonObject } from '@backstage/types';
import { LifecycleService } from '@backstage/backend-plugin-api';
import { LoadConfigOptionsRemote } from '@backstage/config-loader';
import { LoggerService } from '@backstage/backend-plugin-api';
import { PermissionsService } from '@backstage/backend-plugin-api';
import { RemoteConfigSourceOptions } from '@backstage/config-loader';
import { RequestHandler } from 'express';
import { RequestListener } from 'http';
import { RootConfigService } from '@backstage/backend-plugin-api';
import { RootHttpRouterService } from '@backstage/backend-plugin-api';
import { RootLifecycleService } from '@backstage/backend-plugin-api';
import { RootLoggerService } from '@backstage/backend-plugin-api';
import { SchedulerService } from '@backstage/backend-plugin-api';
import type { Server } from 'node:http';
import { ServiceFactory } from '@backstage/backend-plugin-api';
import { ServiceFactoryCompat } from '@backstage/backend-plugin-api';
import { TokenManagerService } from '@backstage/backend-plugin-api';
import { transport } from 'winston';
import { UrlReaderService } from '@backstage/backend-plugin-api';
import { UserInfoService } from '@backstage/backend-plugin-api';
// @public @deprecated (undocumented)
export const authServiceFactory: ServiceFactoryCompat<
AuthService,
'plugin',
'singleton',
undefined
>;
// @public (undocumented)
export interface Backend {
@@ -77,29 +50,11 @@ export interface Backend {
stop(): Promise<void>;
}
// @public @deprecated (undocumented)
export const cacheServiceFactory: ServiceFactoryCompat<
CacheService,
'plugin',
'singleton',
undefined
>;
// Warning: (ae-forgotten-export) The symbol "createConfigSecretEnumerator_2" needs to be exported by the entry point index.d.ts
//
// @public @deprecated (undocumented)
export const createConfigSecretEnumerator: typeof createConfigSecretEnumerator_2;
// Warning: (ae-forgotten-export) The symbol "createHttpServer_2" needs to be exported by the entry point index.d.ts
//
// @public @deprecated (undocumented)
export const createHttpServer: typeof createHttpServer_2;
// Warning: (ae-forgotten-export) The symbol "createLifecycleMiddleware_2" needs to be exported by the entry point index.d.ts
//
// @public @deprecated
export const createLifecycleMiddleware: typeof createLifecycleMiddleware_2;
// @public (undocumented)
export function createSpecializedBackend(
options: CreateSpecializedBackendOptions,
@@ -111,72 +66,11 @@ export interface CreateSpecializedBackendOptions {
defaultServiceFactories: ServiceFactory[];
}
// @public @deprecated (undocumented)
export const databaseServiceFactory: ServiceFactoryCompat<
DatabaseService,
'plugin',
'singleton',
undefined
>;
// @public @deprecated
export class DefaultRootHttpRouter implements RootHttpRouterService {
// (undocumented)
static create(options?: DefaultRootHttpRouterOptions): DefaultRootHttpRouter;
// (undocumented)
handler(): Handler;
// (undocumented)
use(path: string, handler: Handler): void;
}
// Warning: (ae-forgotten-export) The symbol "DefaultRootHttpRouterOptions_2" needs to be exported by the entry point index.d.ts
//
// @public @deprecated
export type DefaultRootHttpRouterOptions = DefaultRootHttpRouterOptions_2;
// @public @deprecated (undocumented)
export const discoveryServiceFactory: ServiceFactoryCompat<
DiscoveryService,
'plugin',
'singleton',
undefined
>;
// Warning: (ae-forgotten-export) The symbol "ExtendedHttpServer_2" needs to be exported by the entry point index.d.ts
//
// @public @deprecated (undocumented)
export type ExtendedHttpServer = ExtendedHttpServer_2;
// @public @deprecated
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 @deprecated (undocumented)
export const httpAuthServiceFactory: ServiceFactoryCompat<
HttpAuthService,
'plugin',
'singleton',
undefined
>;
// @public @deprecated
export const httpRouterServiceFactory: ServiceFactoryCompat<
HttpRouterService,
'plugin',
'singleton',
undefined
>;
// Warning: (ae-forgotten-export) The symbol "HttpServerCertificateOptions_2" needs to be exported by the entry point index.d.ts
//
// @public @deprecated (undocumented)
@@ -201,19 +95,6 @@ export const identityServiceFactory: ServiceFactoryCompat<
IdentityFactoryOptions
>;
// Warning: (ae-forgotten-export) The symbol "LifecycleMiddlewareOptions_2" needs to be exported by the entry point index.d.ts
//
// @public @deprecated
export type LifecycleMiddlewareOptions = LifecycleMiddlewareOptions_2;
// @public @deprecated
export const lifecycleServiceFactory: ServiceFactoryCompat<
LifecycleService,
'plugin',
'singleton',
undefined
>;
// @public @deprecated
export function loadBackendConfig(options: {
remote?: LoadConfigOptionsRemote;
@@ -224,14 +105,6 @@ export function loadBackendConfig(options: {
config: Config;
}>;
// @public @deprecated
export const loggerServiceFactory: ServiceFactoryCompat<
LoggerService,
'plugin',
'singleton',
undefined
>;
// @public @deprecated (undocumented)
export class MiddlewareFactory {
compression(): RequestHandler;
@@ -253,14 +126,6 @@ export type MiddlewareFactoryErrorOptions = MiddlewareFactoryErrorOptions_2;
// @public @deprecated (undocumented)
export type MiddlewareFactoryOptions = MiddlewareFactoryOptions_2;
// @public @deprecated (undocumented)
export const permissionsServiceFactory: ServiceFactoryCompat<
PermissionsService,
'plugin',
'singleton',
undefined
>;
// Warning: (ae-forgotten-export) The symbol "readCorsOptions_2" needs to be exported by the entry point index.d.ts
//
// @public @deprecated (undocumented)
@@ -276,62 +141,6 @@ export const readHelmetOptions: typeof readHelmetOptions_2;
// @public @deprecated (undocumented)
export const readHttpServerOptions: typeof readHttpServerOptions_2;
// @public @deprecated (undocumented)
export interface RootConfigFactoryOptions {
argv?: string[];
remote?: Pick<RemoteConfigSourceOptions, 'reloadInterval'>;
// (undocumented)
watch?: boolean;
}
// @public @deprecated (undocumented)
export const rootConfigServiceFactory: ServiceFactoryCompat<
RootConfigService,
'root',
'singleton',
RootConfigFactoryOptions
>;
// Warning: (ae-forgotten-export) The symbol "RootHttpRouterConfigureContext_2" needs to be exported by the entry point index.d.ts
//
// @public @deprecated (undocumented)
export type RootHttpRouterConfigureContext = RootHttpRouterConfigureContext_2;
// Warning: (ae-forgotten-export) The symbol "RootHttpRouterFactoryOptions_2" needs to be exported by the entry point index.d.ts
//
// @public @deprecated
export type RootHttpRouterFactoryOptions = RootHttpRouterFactoryOptions_2;
// @public @deprecated (undocumented)
export const rootHttpRouterServiceFactory: ((
options?: RootHttpRouterFactoryOptions_2 | undefined,
) => ServiceFactory<RootHttpRouterService, 'root', 'singleton'>) &
ServiceFactory<RootHttpRouterService, 'root', 'singleton'>;
// @public @deprecated
export const rootLifecycleServiceFactory: ServiceFactoryCompat<
RootLifecycleService,
'root',
'singleton',
undefined
>;
// @public @deprecated
export const rootLoggerServiceFactory: ServiceFactoryCompat<
RootLoggerService,
'root',
'singleton',
undefined
>;
// @public @deprecated (undocumented)
export const schedulerServiceFactory: ServiceFactoryCompat<
SchedulerService,
'plugin',
'singleton',
undefined
>;
// @public @deprecated (undocumented)
export const tokenManagerServiceFactory: ServiceFactoryCompat<
TokenManagerService,
@@ -340,22 +149,6 @@ export const tokenManagerServiceFactory: ServiceFactoryCompat<
undefined
>;
// @public @deprecated (undocumented)
export const urlReaderServiceFactory: ServiceFactoryCompat<
UrlReaderService,
'plugin',
'singleton',
undefined
>;
// @public @deprecated (undocumented)
export const userInfoServiceFactory: ServiceFactoryCompat<
UserInfoService,
'plugin',
'singleton',
undefined
>;
// @public @deprecated
export class WinstonLogger implements RootLoggerService {
// (undocumented)
+1 -5
View File
@@ -34,11 +34,7 @@ import {
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
*/
export const readHttpServerOptions = _readHttpServerOptions;
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
*/
export const createHttpServer = _createHttpServer;
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
@@ -1,24 +0,0 @@
/*
* Copyright 2024 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.
*/
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { authServiceFactory as _authServiceFactory } from '../../../../../backend-defaults/src/entrypoints/auth';
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/auth` instead.
*/
export const authServiceFactory = _authServiceFactory;
@@ -1,17 +0,0 @@
/*
* Copyright 2024 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 { authServiceFactory } from './authServiceFactory';
@@ -1,40 +0,0 @@
/*
* Copyright 2022 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 { CacheManager } from '@backstage/backend-common';
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/cache` instead.
*/
export const cacheServiceFactory = createServiceFactory({
service: coreServices.cache,
deps: {
config: coreServices.rootConfig,
logger: coreServices.rootLogger,
plugin: coreServices.pluginMetadata,
},
async createRootContext({ config, logger }) {
return CacheManager.fromConfig(config, { logger });
},
async factory({ plugin }, manager) {
return manager.forPlugin(plugin.getId()).getClient();
},
});
@@ -1,17 +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.
*/
export { cacheServiceFactory } from './cacheServiceFactory';
@@ -1,18 +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.
*/
export { rootConfigServiceFactory } from './rootConfigServiceFactory';
export type { RootConfigFactoryOptions } from './rootConfigServiceFactory';
@@ -1,61 +0,0 @@
/*
* Copyright 2022 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 {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import {
ConfigSources,
RemoteConfigSourceOptions,
} from '@backstage/config-loader';
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/rootConfig` instead.
*/
export interface RootConfigFactoryOptions {
/**
* Process arguments to use instead of the default `process.argv()`.
*/
argv?: string[];
/**
* Enables and sets options for remote configuration loading.
*/
remote?: Pick<RemoteConfigSourceOptions, 'reloadInterval'>;
watch?: boolean;
}
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/rootConfig` instead.
*/
export const rootConfigServiceFactory = createServiceFactory(
(options?: RootConfigFactoryOptions) => ({
service: coreServices.rootConfig,
deps: {},
async factory() {
const source = ConfigSources.default({
argv: options?.argv,
remote: options?.remote,
watch: options?.watch,
});
console.log(`Loading config from ${source}`);
return await ConfigSources.toConfig(source);
},
}),
);
@@ -1,52 +0,0 @@
/*
* Copyright 2022 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 { DatabaseManager } from '@backstage/backend-common';
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { ConfigReader } from '@backstage/config';
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/database` instead.
*/
export const databaseServiceFactory = createServiceFactory({
service: coreServices.database,
deps: {
config: coreServices.rootConfig,
lifecycle: coreServices.lifecycle,
pluginMetadata: coreServices.pluginMetadata,
},
async createRootContext({ config }) {
return config.getOptional('backend.database')
? DatabaseManager.fromConfig(config)
: DatabaseManager.fromConfig(
new ConfigReader({
backend: {
database: { client: 'better-sqlite3', connection: ':memory:' },
},
}),
);
},
async factory({ pluginMetadata, lifecycle }, databaseManager) {
return databaseManager.forPlugin(pluginMetadata.getId(), {
pluginMetadata,
lifecycle,
});
},
});
@@ -1,17 +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.
*/
export { databaseServiceFactory } from './databaseServiceFactory';
@@ -1,24 +0,0 @@
/*
* Copyright 2024 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 * from './auth';
export * from './httpAuth';
export * from './httpRouter';
export * from './logger';
export * from './rootHttpRouter';
export * from './rootLogger';
export * from './scheduler';
export * from './userInfo';
@@ -1,257 +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.
*/
import { ConfigReader } from '@backstage/config';
import { HostDiscovery } from './HostDiscovery';
describe('HostDiscovery', () => {
it('is created from config', async () => {
const discovery = HostDiscovery.fromConfig(
new ConfigReader({
backend: {
baseUrl: 'http://localhost:40',
listen: { port: 80, host: 'localhost' },
},
}),
);
await expect(discovery.getBaseUrl('catalog')).resolves.toBe(
'http://localhost:80/api/catalog',
);
await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe(
'http://localhost:40/api/catalog',
);
});
it('strips trailing slashes in config', async () => {
const discovery = HostDiscovery.fromConfig(
new ConfigReader({
backend: {
baseUrl: 'http://localhost:40//',
listen: { port: 80, host: 'localhost' },
},
}),
);
await expect(discovery.getBaseUrl('catalog')).resolves.toBe(
'http://localhost:80/api/catalog',
);
await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe(
'http://localhost:40/api/catalog',
);
});
it('can configure the base path', async () => {
const discovery = HostDiscovery.fromConfig(
new ConfigReader({
backend: {
baseUrl: 'http://localhost:40',
listen: { port: 80, host: 'localhost' },
},
}),
{ basePath: '/service' },
);
await expect(discovery.getBaseUrl('catalog')).resolves.toBe(
'http://localhost:80/service/catalog',
);
await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe(
'http://localhost:40/service/catalog',
);
});
it.each([
[{ listen: ':80' }, 'http://localhost:80'],
[{ listen: ':40', https: true }, 'https://localhost:40'],
[{ listen: '127.0.0.1:80' }, 'http://127.0.0.1:80'],
[{ listen: '127.0.0.1:80', https: true }, 'https://127.0.0.1:80'],
[{ listen: '0.0.0.0:40' }, 'http://127.0.0.1:40'],
[{ listen: { port: 80 } }, 'http://localhost:80'],
[{ listen: { port: 8000 } }, 'http://localhost:8000'],
[{ listen: { port: 80, host: '0.0.0.0' } }, 'http://127.0.0.1:80'],
[{ listen: { port: 80, host: '::' } }, 'http://localhost:80'],
[{ listen: { port: 80, host: '::1' } }, 'http://[::1]:80'],
[{ listen: { port: 90, host: '::2' }, https: true }, 'https://[::2]:90'],
])('resolves internal baseUrl for %j as %s', async (config, expected) => {
const discovery = HostDiscovery.fromConfig(
new ConfigReader({
backend: {
baseUrl: 'http://localhost:40',
...config,
},
}),
);
await expect(discovery.getBaseUrl('catalog')).resolves.toBe(
`${expected}/api/catalog`,
);
});
it('uses plugin specific targets from config if provided', async () => {
const discovery = HostDiscovery.fromConfig(
new ConfigReader({
backend: {
baseUrl: 'http://localhost:40',
listen: { port: 80, host: 'localhost' },
},
discovery: {
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'],
},
],
},
}),
);
await expect(discovery.getBaseUrl('catalog')).resolves.toBe(
'http://catalog-backend:8080/api/catalog',
);
await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe(
'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}} or {{ 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',
);
});
it('encodes the pluginId', 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: ['plugin/beta'],
},
],
},
}),
);
await expect(discovery.getBaseUrl('plugin/beta')).resolves.toBe(
'http://common-backend:8080/api/plugin%2Fbeta',
);
await expect(discovery.getBaseUrl('plugin/alpha')).resolves.toBe(
'http://localhost:80/api/plugin%2Falpha',
);
await expect(discovery.getExternalBaseUrl('plugin/alpha')).resolves.toBe(
'http://localhost:40/api/plugin%2Falpha',
);
});
});
@@ -1,71 +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.
*/
import { Config } from '@backstage/config';
import { DiscoveryService } from '@backstage/backend-plugin-api';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { HostDiscovery as _HostDiscovery } from '../../../../../backend-defaults/src/entrypoints/discovery';
/**
* 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
* @deprecated Please import from `@backstage/backend-defaults/discovery` instead.
*/
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 }) {
return new HostDiscovery(_HostDiscovery.fromConfig(config, options));
}
private constructor(private readonly impl: _HostDiscovery) {}
async getBaseUrl(pluginId: string): Promise<string> {
return this.impl.getBaseUrl(pluginId);
}
async getExternalBaseUrl(pluginId: string): Promise<string> {
return this.impl.getExternalBaseUrl(pluginId);
}
}
@@ -1,35 +0,0 @@
/*
* Copyright 2022 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 {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { HostDiscovery } from './HostDiscovery';
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/discovery` instead.
*/
export const discoveryServiceFactory = createServiceFactory({
service: coreServices.discovery,
deps: {
config: coreServices.rootConfig,
},
async factory({ config }) {
return HostDiscovery.fromConfig(config);
},
});
@@ -1,18 +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.
*/
export { discoveryServiceFactory } from './discoveryServiceFactory';
export { HostDiscovery } from './HostDiscovery';
@@ -1,24 +0,0 @@
/*
* Copyright 2024 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.
*/
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { httpAuthServiceFactory as _httpAuthServiceFactory } from '../../../../../backend-defaults/src/entrypoints/httpAuth';
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/httpAuth` instead.
*/
export const httpAuthServiceFactory = _httpAuthServiceFactory;
@@ -1,17 +0,0 @@
/*
* Copyright 2024 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 { httpAuthServiceFactory } from './httpAuthServiceFactory';
@@ -1,45 +0,0 @@
/*
* Copyright 2022 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.
*/
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import {
createLifecycleMiddleware as _createLifecycleMiddleware,
type LifecycleMiddlewareOptions as _LifecycleMiddlewareOptions,
} from '../../../../../backend-defaults/src/entrypoints/httpRouter/createLifecycleMiddleware';
/**
* Options for {@link createLifecycleMiddleware}.
* @public
* @deprecated Please import from `@backstage/backend-defaults/httpRouter` instead.
*/
export type LifecycleMiddlewareOptions = _LifecycleMiddlewareOptions;
/**
* Creates a middleware that pauses requests until the service has started.
*
* @remarks
*
* Requests that arrive before the service has started will be paused until startup is complete.
* If the service does not start within the provided timeout, the request will be rejected with a
* {@link @backstage/errors#ServiceUnavailableError}.
*
* If the service is shutting down, all requests will be rejected with a
* {@link @backstage/errors#ServiceUnavailableError}.
*
* @public
* @deprecated Please import from `@backstage/backend-defaults/httpRouter` instead.
*/
export const createLifecycleMiddleware = _createLifecycleMiddleware;
@@ -1,30 +0,0 @@
/*
* Copyright 2022 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.
*/
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { httpRouterServiceFactory as _httpRouterServiceFactory } from '../../../../../backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory';
/**
* HTTP route registration for plugins.
*
* See {@link @backstage/code-plugin-api#HttpRouterService}
* and {@link https://backstage.io/docs/backend-system/core-services/http-router | the service docs}
* for more information.
*
* @public
* @deprecated Please import from `@backstage/backend-defaults/httpRouter` instead.
*/
export const httpRouterServiceFactory = _httpRouterServiceFactory;
@@ -1,19 +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.
*/
export { httpRouterServiceFactory } from './httpRouterServiceFactory';
export { createLifecycleMiddleware } from './createLifecycleMiddleware';
export type { LifecycleMiddlewareOptions } from './createLifecycleMiddleware';
@@ -14,15 +14,5 @@
* limitations under the License.
*/
export * from './cache';
export * from './config';
export * from './database';
export * from './discovery';
export * from './identity';
export * from './lifecycle';
export * from './permissions';
export * from './rootLifecycle';
export * from './tokenManager';
export * from './urlReader';
export * from './deprecated';
@@ -1,17 +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.
*/
export { lifecycleServiceFactory } from './lifecycleServiceFactory';
@@ -1,109 +0,0 @@
/*
* Copyright 2022 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 {
LifecycleService,
LifecycleServiceShutdownHook,
LifecycleServiceShutdownOptions,
LifecycleServiceStartupHook,
LifecycleServiceStartupOptions,
LoggerService,
PluginMetadataService,
RootLifecycleService,
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
/**
* @internal
* @deprecated
*/
export class BackendPluginLifecycleImpl implements LifecycleService {
constructor(
private readonly logger: LoggerService,
private readonly rootLifecycle: RootLifecycleService,
private readonly pluginMetadata: PluginMetadataService,
) {}
#hasStarted = false;
#startupTasks: Array<{
hook: LifecycleServiceStartupHook;
options?: LifecycleServiceStartupOptions;
}> = [];
addStartupHook(
hook: LifecycleServiceStartupHook,
options?: LifecycleServiceStartupOptions,
): void {
if (this.#hasStarted) {
throw new Error('Attempted to add startup hook after startup');
}
this.#startupTasks.push({ hook, options });
}
async startup(): Promise<void> {
if (this.#hasStarted) {
return;
}
this.#hasStarted = true;
this.logger.debug(
`Running ${this.#startupTasks.length} plugin startup tasks...`,
);
await Promise.all(
this.#startupTasks.map(async ({ hook, options }) => {
const logger = options?.logger ?? this.logger;
try {
await hook();
logger.debug(`Plugin startup hook succeeded`);
} catch (error) {
logger.error(`Plugin startup hook failed, ${error}`);
}
}),
);
}
addShutdownHook(
hook: LifecycleServiceShutdownHook,
options?: LifecycleServiceShutdownOptions,
): void {
const plugin = this.pluginMetadata.getId();
this.rootLifecycle.addShutdownHook(hook, {
logger: options?.logger?.child({ plugin }) ?? this.logger,
});
}
}
/**
* Allows plugins to register shutdown hooks that are run when the process is about to exit.
*
* @public
* @deprecated Please import from `@backstage/backend-defaults/lifecycle` instead.
*/
export const lifecycleServiceFactory = createServiceFactory({
service: coreServices.lifecycle,
deps: {
logger: coreServices.logger,
rootLifecycle: coreServices.rootLifecycle,
pluginMetadata: coreServices.pluginMetadata,
},
async factory({ rootLifecycle, logger, pluginMetadata }) {
return new BackendPluginLifecycleImpl(
logger,
rootLifecycle,
pluginMetadata,
);
},
});
@@ -1,17 +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.
*/
export { loggerServiceFactory } from './loggerServiceFactory';
@@ -1,30 +0,0 @@
/*
* Copyright 2022 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.
*/
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { loggerServiceFactory as _loggerServiceFactory } from '../../../../../backend-defaults/src/entrypoints/logger/loggerServiceFactory';
/**
* Plugin-level logging.
*
* See {@link @backstage/code-plugin-api#LoggerService}
* and {@link https://backstage.io/docs/backend-system/core-services/logger | the service docs}
* for more information.
*
* @public
* @deprecated Please import from `@backstage/backend-defaults/logger` instead.
*/
export const loggerServiceFactory = _loggerServiceFactory;
@@ -1,17 +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.
*/
export { permissionsServiceFactory } from './permissionsServiceFactory';
@@ -1,42 +0,0 @@
/*
* Copyright 2022 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 {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/permissions` instead.
*/
export const permissionsServiceFactory = createServiceFactory({
service: coreServices.permissions,
deps: {
auth: coreServices.auth,
config: coreServices.rootConfig,
discovery: coreServices.discovery,
tokenManager: coreServices.tokenManager,
},
async factory({ auth, config, discovery, tokenManager }) {
return ServerPermissionClient.fromConfig(config, {
auth,
discovery,
tokenManager,
});
},
});
@@ -1,124 +0,0 @@
/*
* Copyright 2022 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 express from 'express';
import request from 'supertest';
import { DefaultRootHttpRouter } from './DefaultRootHttpRouter';
describe('DefaultRootHttpRouter', () => {
it.each([
[['/b'], '/a'],
[['/a'], '/aa/b'],
[['/aa'], '/a/b'],
[['/a/b'], '/aa'],
[['/b/a'], '/a'],
[['/a'], '/aa'],
])(`with existing paths %s, adds %s without conflict`, (existing, added) => {
const router = DefaultRootHttpRouter.create();
for (const path of existing) {
router.use(path, () => {});
}
expect(() => router.use(added, () => {})).not.toThrow();
});
it.each([
[['/a'], '/a', '/a'],
[['/a'], '/a/b', '/a'],
[['/a/b'], '/a', '/a/b'],
])(
`find conflict when existing paths %s, adds %s`,
(existing, added, conflict) => {
const router = DefaultRootHttpRouter.create();
for (const path of existing) {
router.use(path, () => {});
}
expect(() => router.use(added, () => {})).toThrow(
`Path ${added} conflicts with the existing path ${conflict}`,
);
},
);
it('should not be possible to supply an empty indexPath', () => {
expect(() => DefaultRootHttpRouter.create({ indexPath: '' })).toThrow(
'indexPath option may not be an empty string',
);
});
it('will always prioritize non-index paths', async () => {
const router = DefaultRootHttpRouter.create({ indexPath: '/x' });
const app = express();
app.use(router.handler());
const routerX = express.Router();
routerX.get('/a', (_req, res) => res.status(201).end());
const routerY = express.Router();
routerY.get('/a', (_req, res) => res.status(202).end());
await request(app).get('/').expect(404);
await request(app).get('/a').expect(404);
await request(app).get('/x/a').expect(404);
await request(app).get('/y/a').expect(404);
router.use('/x', routerX);
await request(app).get('/').expect(404);
await request(app).get('/a').expect(201);
await request(app).get('/x/a').expect(201);
await request(app).get('/y/a').expect(404);
router.use('/y', routerY);
await request(app).get('/').expect(404);
await request(app).get('/a').expect(201);
await request(app).get('/x/a').expect(201);
await request(app).get('/y/a').expect(202);
expect('test').toBe('test');
});
it('should treat unknown /api/ routes as 404', async () => {
const router = DefaultRootHttpRouter.create();
const app = express();
app.use(router.handler());
router.use('/api/app', (_req, res) => res.status(201).end());
router.use('/api/catalog', (_req, res) => res.status(202).end());
await request(app).get('/').expect(201);
await request(app).get('/api/catalog').expect(202);
await request(app).get('/unknown').expect(201);
await request(app).get('/api/unknown').expect(404);
expect('test').toBe('test');
});
it('should treat unknown /api/ routes as 404 without an index path', async () => {
const router = DefaultRootHttpRouter.create({ indexPath: false });
const app = express();
app.use(router.handler());
router.use('/api/app', (_req, res) => res.status(201).end());
router.use('/api/catalog', (_req, res) => res.status(202).end());
await request(app).get('/').expect(404);
await request(app).get('/api/catalog').expect(202);
await request(app).get('/unknown').expect(404);
await request(app).get('/api/unknown').expect(404);
expect('test').toBe('test');
});
});
@@ -1,54 +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 { RootHttpRouterService } from '@backstage/backend-plugin-api';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import {
DefaultRootHttpRouter as _DefaultRootHttpRouter,
DefaultRootHttpRouterOptions as _DefaultRootHttpRouterOptions,
} from '../../../../../backend-defaults/src/entrypoints/rootHttpRouter/DefaultRootHttpRouter';
import { Handler } from 'express';
/**
* Options for the {@link DefaultRootHttpRouter} class.
*
* @public
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
*/
export type DefaultRootHttpRouterOptions = _DefaultRootHttpRouterOptions;
/**
* The default implementation of the {@link @backstage/backend-plugin-api#RootHttpRouterService} interface for
* {@link @backstage/backend-plugin-api#coreServices.rootHttpRouter}.
*
* @public
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
*/
export class DefaultRootHttpRouter implements RootHttpRouterService {
static create(options?: DefaultRootHttpRouterOptions) {
return new DefaultRootHttpRouter(_DefaultRootHttpRouter.create(options));
}
private constructor(private readonly impl: RootHttpRouterService) {}
use(path: string, handler: Handler) {
this.impl.use(path, handler);
}
handler(): Handler {
return (this.impl as any).handler();
}
}
@@ -1,25 +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.
*/
export {
rootHttpRouterServiceFactory,
type RootHttpRouterFactoryOptions,
type RootHttpRouterConfigureContext,
} from './rootHttpRouterServiceFactory';
export {
DefaultRootHttpRouter,
type DefaultRootHttpRouterOptions,
} from './DefaultRootHttpRouter';
@@ -1,46 +0,0 @@
/*
* Copyright 2022 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.
*/
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import {
rootHttpRouterServiceFactory as _rootHttpRouterServiceFactory,
RootHttpRouterFactoryOptions as _RootHttpRouterFactoryOptions,
RootHttpRouterConfigureContext as _RootHttpRouterConfigureContext,
} from '../../../../../backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory';
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
*/
export type RootHttpRouterConfigureContext = _RootHttpRouterConfigureContext;
/**
* HTTP route registration for root services.
*
* See {@link @backstage/code-plugin-api#RootHttpRouterService}
* and {@link https://backstage.io/docs/backend-system/core-services/root-http-router | the service docs}
* for more information.
*
* @public
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
*/
export type RootHttpRouterFactoryOptions = _RootHttpRouterFactoryOptions;
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
*/
export const rootHttpRouterServiceFactory = _rootHttpRouterServiceFactory;
@@ -1,17 +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.
*/
export { rootLifecycleServiceFactory } from './rootLifecycleServiceFactory';
@@ -1,60 +0,0 @@
/*
* Copyright 2022 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 { BackendLifecycleImpl } from './rootLifecycleServiceFactory';
import { mockServices } from '@backstage/backend-test-utils';
describe('lifecycleService', () => {
it('should execute registered shutdown hook', async () => {
const service = new BackendLifecycleImpl(mockServices.logger.mock());
const hook = jest.fn();
service.addShutdownHook(() => hook());
// should not execute the hook more than once.
await service.shutdown();
await service.shutdown();
await service.shutdown();
expect(hook).toHaveBeenCalledTimes(1);
});
it('should not throw errors', async () => {
const service = new BackendLifecycleImpl(mockServices.logger.mock());
service.addShutdownHook(() => {
throw new Error('oh no');
});
await expect(service.shutdown()).resolves.toBeUndefined();
});
it('should not throw async errors', async () => {
const service = new BackendLifecycleImpl(mockServices.logger.mock());
service.addShutdownHook(async () => {
throw new Error('oh no');
});
await expect(service.shutdown()).resolves.toBeUndefined();
});
it('should reject hooks after trigger', async () => {
const service = new BackendLifecycleImpl(mockServices.logger.mock());
await service.startup();
expect(() => {
service.addStartupHook(() => {});
}).toThrow('Attempted to add startup hook after startup');
await service.shutdown();
expect(() => {
service.addShutdownHook(() => {});
}).toThrow('Attempted to add shutdown hook after shutdown');
});
});
@@ -1,124 +0,0 @@
/*
* Copyright 2022 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 {
createServiceFactory,
coreServices,
LifecycleServiceStartupHook,
LifecycleServiceStartupOptions,
LifecycleServiceShutdownHook,
LifecycleServiceShutdownOptions,
RootLifecycleService,
LoggerService,
} from '@backstage/backend-plugin-api';
/**
* @internal
* @deprecated
*/
export class BackendLifecycleImpl implements RootLifecycleService {
constructor(private readonly logger: LoggerService) {}
#hasStarted = false;
#startupTasks: Array<{
hook: LifecycleServiceStartupHook;
options?: LifecycleServiceStartupOptions;
}> = [];
addStartupHook(
hook: LifecycleServiceStartupHook,
options?: LifecycleServiceStartupOptions,
): void {
if (this.#hasStarted) {
throw new Error('Attempted to add startup hook after startup');
}
this.#startupTasks.push({ hook, options });
}
async startup(): Promise<void> {
if (this.#hasStarted) {
return;
}
this.#hasStarted = true;
this.logger.debug(`Running ${this.#startupTasks.length} startup tasks...`);
await Promise.all(
this.#startupTasks.map(async ({ hook, options }) => {
const logger = options?.logger ?? this.logger;
try {
await hook();
logger.debug(`Startup hook succeeded`);
} catch (error) {
logger.error(`Startup hook failed, ${error}`);
}
}),
);
}
#hasShutdown = false;
#shutdownTasks: Array<{
hook: LifecycleServiceShutdownHook;
options?: LifecycleServiceShutdownOptions;
}> = [];
addShutdownHook(
hook: LifecycleServiceShutdownHook,
options?: LifecycleServiceShutdownOptions,
): void {
if (this.#hasShutdown) {
throw new Error('Attempted to add shutdown hook after shutdown');
}
this.#shutdownTasks.push({ hook, options });
}
async shutdown(): Promise<void> {
if (this.#hasShutdown) {
return;
}
this.#hasShutdown = true;
this.logger.debug(
`Running ${this.#shutdownTasks.length} shutdown tasks...`,
);
await Promise.all(
this.#shutdownTasks.map(async ({ hook, options }) => {
const logger = options?.logger ?? this.logger;
try {
await hook();
logger.debug(`Shutdown hook succeeded`);
} catch (error) {
logger.error(`Shutdown hook failed, ${error}`);
}
}),
);
}
}
/**
* Allows plugins to register shutdown hooks that are run when the process is about to exit.
*
* @public
* @deprecated Please import from `@backstage/backend-defaults/rootLifecycle` instead.
*/
export const rootLifecycleServiceFactory = createServiceFactory({
service: coreServices.rootLifecycle,
deps: {
logger: coreServices.rootLogger,
},
async factory({ logger }) {
return new BackendLifecycleImpl(logger);
},
});
@@ -1,17 +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.
*/
export { rootLoggerServiceFactory } from './rootLoggerServiceFactory';
@@ -1,30 +0,0 @@
/*
* Copyright 2022 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.
*/
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { rootLoggerServiceFactory as _rootLoggerServiceFactory } from '../../../../../backend-defaults/src/entrypoints/rootLogger/rootLoggerServiceFactory';
/**
* Root-level logging.
*
* See {@link @backstage/code-plugin-api#RootLoggerService}
* and {@link https://backstage.io/docs/backend-system/core-services/root-logger | the service docs}
* for more information.
*
* @public
* @deprecated Please import from `@backstage/backend-defaults/rootLogger` instead.
*/
export const rootLoggerServiceFactory = _rootLoggerServiceFactory;
@@ -1,17 +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.
*/
export { schedulerServiceFactory } from './schedulerServiceFactory';
@@ -1,46 +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 { coreServices } from '@backstage/backend-plugin-api';
import { ServiceFactoryTester } from '@backstage/backend-test-utils';
import { schedulerServiceFactory } from './schedulerServiceFactory';
describe('schedulerFactory', () => {
it('creates sidecar database features', async () => {
const tester = ServiceFactoryTester.from(schedulerServiceFactory());
const scheduler = await tester.getSubject();
await scheduler.scheduleTask({
id: 'task1',
timeout: { seconds: 1 },
frequency: { seconds: 1 },
fn: async () => {},
});
const database = await tester.getService(coreServices.database);
const client = await database.getClient();
await expect(
client.from('backstage_backend_tasks__tasks').count(),
).resolves.toEqual([{ 'count(*)': 1 }]);
await expect(
client.from('backstage_backend_tasks__knex_migrations').count(),
).resolves.toEqual([{ 'count(*)': expect.any(Number) }]);
await expect(
client.from('backstage_backend_tasks__knex_migrations_lock').count(),
).resolves.toEqual([{ 'count(*)': expect.any(Number) }]);
});
});
@@ -1,41 +0,0 @@
/*
* Copyright 2022 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 {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { TaskScheduler } from '@backstage/backend-tasks';
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/scheduler` instead.
*/
export const schedulerServiceFactory = createServiceFactory({
service: coreServices.scheduler,
deps: {
plugin: coreServices.pluginMetadata,
databaseManager: coreServices.database,
logger: coreServices.logger,
},
async factory({ plugin, databaseManager, logger }) {
return TaskScheduler.forPlugin({
pluginId: plugin.getId(),
databaseManager,
logger,
});
},
});
@@ -1,17 +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.
*/
export { urlReaderServiceFactory } from './urlReaderServiceFactory';
@@ -1,39 +0,0 @@
/*
* Copyright 2022 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 { UrlReaders } from '@backstage/backend-common';
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/urlReader` instead.
*/
export const urlReaderServiceFactory = createServiceFactory({
service: coreServices.urlReader,
deps: {
config: coreServices.rootConfig,
logger: coreServices.logger,
},
async factory({ config, logger }) {
return UrlReaders.default({
config,
logger,
});
},
});
@@ -1,17 +0,0 @@
/*
* Copyright 2024 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 { userInfoServiceFactory } from './userInfoServiceFactory';
@@ -1,24 +0,0 @@
/*
* Copyright 2024 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.
*/
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { userInfoServiceFactory as _userInfoServiceFactory } from '../../../../../backend-defaults/src/entrypoints/userInfo';
/**
* @public
* @deprecated Please import from `@backstage/backend-defaults/userInfo` instead.
*/
export const userInfoServiceFactory = _userInfoServiceFactory;
@@ -24,7 +24,7 @@ import { Router } from 'express';
import { createLegacyAuthAdapters } from '..';
import { legacyPlugin } from './legacy';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { authServiceFactory } from '../../../../backend-app-api/src/services/implementations/auth';
import { authServiceFactory } from '../../../../backend-defaults/src/entrypoints/auth';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { tokenManagerServiceFactory } from '../../../../backend-app-api/src/services/implementations/tokenManager';
@@ -70,6 +70,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/backend-defaults": "workspace:^",
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"wait-for-expect": "^3.0.2"
@@ -37,15 +37,13 @@ import {
import { ScannedPluginManifest, ScannedPluginPackage } from '../scanner/types';
import { randomUUID } from 'crypto';
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
import {
createSpecializedBackend,
rootLifecycleServiceFactory,
} from '@backstage/backend-app-api';
import { createSpecializedBackend } from '@backstage/backend-app-api';
import { ConfigSources } from '@backstage/config-loader';
import { Logs, MockedLogger, LogContent } from '../__testUtils__/testUtils';
import { PluginScanner } from '../scanner/plugin-scanner';
import { findPaths } from '@backstage/cli-common';
import { createMockDirectory } from '@backstage/backend-test-utils';
import { rootLifecycleServiceFactory } from '@backstage/backend-defaults';
describe('backend-dynamic-feature-service', () => {
const mockDir = createMockDirectory();
@@ -18,10 +18,7 @@ import {
Backend,
createSpecializedBackend,
MiddlewareFactory,
createHttpServer,
ExtendedHttpServer,
HostDiscovery,
DefaultRootHttpRouter,
} from '@backstage/backend-app-api';
import {
createServiceFactory,
@@ -40,7 +37,12 @@ import {
InternalBackendFeature,
InternalBackendRegistrations,
} from '@backstage/backend-plugin-api/src/wiring/types';
import { createHealthRouter } from '@backstage/backend-defaults/rootHttpRouter';
import {
DefaultRootHttpRouter,
createHealthRouter,
createHttpServer,
} from '@backstage/backend-defaults/rootHttpRouter';
import { HostDiscovery } from '@backstage/backend-defaults/discovery';
/** @public */
export interface TestBackendOptions<TExtensionPoints extends any[]> {
@@ -14,10 +14,8 @@
* limitations under the License.
*/
import {
authServiceFactory,
httpAuthServiceFactory,
} from '@backstage/backend-app-api';
import { authServiceFactory } from '@backstage/backend-defaults/auth';
import { httpAuthServiceFactory } from '@backstage/backend-defaults/httpAuth';
import {
mockServices,
registerMswTestHooks,
+1
View File
@@ -3715,6 +3715,7 @@ __metadata:
dependencies:
"@backstage/backend-app-api": "workspace:^"
"@backstage/backend-common": "workspace:^"
"@backstage/backend-defaults": "workspace:^"
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/backend-tasks": "workspace:^"
"@backstage/backend-test-utils": "workspace:^"