[NBS 1.0] move more implementations from app-api to defaults
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
---
|
||||
|
||||
Use imports from backend-defaults instead of the deprecated ones from backend-app-api
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
"build:all": "backstage-cli repo build --all",
|
||||
"build:api-docs": "LANG=en_EN yarn build:api-reports --docs --exclude 'plugins/@(adr|adr-backend|adr-common|airbrake|airbrake-backend|allure|analytics-module-ga|analytics-module-ga4|analytics-module-newrelic-browser|apache-airflow|api-docs|api-docs-module-protoc-gen-doc|apollo-explorer|app-visualizer|azure-devops|azure-devops-backend|azure-devops-common|azure-sites|azure-sites-backend|azure-sites-common|badges|badges-backend|bazaar|bazaar-backend|bitbucket-cloud-common|bitrise|catalog-graph|catalog-graphql|catalog-import|catalog-unprocessed-entities|cicd-statistics|cicd-statistics-module-gitlab|circleci|cloudbuild|code-climate|code-coverage|code-coverage-backend|codescene|config-schema|cost-insights|cost-insights-common|dynatrace|entity-feedback|entity-feedback-backend|entity-feedback-common|entity-validation|example-todo-list|example-todo-list-backend|example-todo-list-common|firehydrant|fossa|gcalendar|gcp-projects|git-release-manager|github-actions|github-deployments|github-issues|github-pull-requests-board|gitops-profiles|gocd|graphiql|graphql-backend|graphql-voyager|ilert|jenkins|jenkins-backend|jenkins-common|kafka|kafka-backend|lighthouse|lighthouse-backend|lighthouse-common|linguist|linguist-backend|linguist-common|microsoft-calendar|newrelic|newrelic-dashboard|nomad|nomad-backend|octopus-deploy|opencost|pagerduty|periskop|periskop-backend|playlist|playlist-backend|playlist-common|proxy-backend|puppetdb|rollbar|rollbar-backend|sentry|shortcuts|splunk-on-call|stack-overflow|stack-overflow-backend|stackstorm|tech-radar|tech-radar-2|todo|todo-backend|xcmetrics)'",
|
||||
"build:api-reports": "yarn build:api-reports:only --tsc",
|
||||
"build:api-reports:only": "NODE_OPTIONS=--max-old-space-size=8192 backstage-repo-tools api-reports --allow-warnings 'packages/backend-common,packages/core-components,plugins/+(catalog|catalog-import|git-release-manager|jenkins|kubernetes)' -o ae-wrong-input-file-type --validate-release-tags",
|
||||
"build:api-reports:only": "NODE_OPTIONS=--max-old-space-size=8192 backstage-repo-tools api-reports --allow-warnings 'packages/backend-app-api,packages/backend-common,packages/core-components,plugins/+(catalog|catalog-import|git-release-manager|jenkins|kubernetes)' -o ae-wrong-input-file-type --validate-release-tags",
|
||||
"build:backend": "yarn workspace example-backend build",
|
||||
"build:knip-reports": "backstage-repo-tools knip-reports",
|
||||
"build:plugins-report": "node ./scripts/build-plugins-report",
|
||||
|
||||
@@ -68,26 +68,20 @@ export interface Backend {
|
||||
// @public @deprecated (undocumented)
|
||||
export const cacheServiceFactory: () => ServiceFactory<CacheService, 'plugin'>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function createConfigSecretEnumerator(options: {
|
||||
logger: LoggerService;
|
||||
dir?: string;
|
||||
schema?: ConfigSchema;
|
||||
}): Promise<(config: Config) => Iterable<string>>;
|
||||
// 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;
|
||||
|
||||
// @public
|
||||
export function createHttpServer(
|
||||
listener: RequestListener,
|
||||
options: HttpServerOptions,
|
||||
deps: {
|
||||
logger: LoggerService;
|
||||
},
|
||||
): Promise<ExtendedHttpServer>;
|
||||
// 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;
|
||||
|
||||
// @public
|
||||
export function createLifecycleMiddleware(
|
||||
options: LifecycleMiddlewareOptions,
|
||||
): RequestHandler;
|
||||
// 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(
|
||||
@@ -106,7 +100,7 @@ export const databaseServiceFactory: () => ServiceFactory<
|
||||
'plugin'
|
||||
>;
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export class DefaultRootHttpRouter implements RootHttpRouterService {
|
||||
// (undocumented)
|
||||
static create(options?: DefaultRootHttpRouterOptions): DefaultRootHttpRouter;
|
||||
@@ -116,10 +110,10 @@ export class DefaultRootHttpRouter implements RootHttpRouterService {
|
||||
use(path: string, handler: Handler): void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface DefaultRootHttpRouterOptions {
|
||||
indexPath?: string | false;
|
||||
}
|
||||
// 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: () => ServiceFactory<
|
||||
@@ -127,15 +121,10 @@ export const discoveryServiceFactory: () => ServiceFactory<
|
||||
'plugin'
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface ExtendedHttpServer extends http.Server {
|
||||
// (undocumented)
|
||||
port(): number;
|
||||
// (undocumented)
|
||||
start(): Promise<void>;
|
||||
// (undocumented)
|
||||
stop(): Promise<void>;
|
||||
}
|
||||
// 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 {
|
||||
@@ -157,38 +146,25 @@ export const httpAuthServiceFactory: () => ServiceFactory<
|
||||
'plugin'
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface HttpRouterFactoryOptions {
|
||||
getPath?(pluginId: string): string;
|
||||
}
|
||||
// Warning: (ae-forgotten-export) The symbol "HttpRouterFactoryOptions_2" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public @deprecated (undocumented)
|
||||
export type HttpRouterFactoryOptions = HttpRouterFactoryOptions_2;
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export const httpRouterServiceFactory: (
|
||||
options?: HttpRouterFactoryOptions | undefined,
|
||||
options?: HttpRouterFactoryOptions_2 | undefined,
|
||||
) => ServiceFactory<HttpRouterService, 'plugin'>;
|
||||
|
||||
// @public
|
||||
export type HttpServerCertificateOptions =
|
||||
| {
|
||||
type: 'pem';
|
||||
key: string;
|
||||
cert: string;
|
||||
}
|
||||
| {
|
||||
type: 'generated';
|
||||
hostname: string;
|
||||
};
|
||||
// Warning: (ae-forgotten-export) The symbol "HttpServerCertificateOptions_2" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public @deprecated (undocumented)
|
||||
export type HttpServerCertificateOptions = HttpServerCertificateOptions_2;
|
||||
|
||||
// @public
|
||||
export type HttpServerOptions = {
|
||||
listen: {
|
||||
port: number;
|
||||
host: string;
|
||||
};
|
||||
https?: {
|
||||
certificate: HttpServerCertificateOptions;
|
||||
};
|
||||
};
|
||||
// Warning: (ae-forgotten-export) The symbol "HttpServerOptions_2" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public @deprecated (undocumented)
|
||||
export type HttpServerOptions = HttpServerOptions_2;
|
||||
|
||||
// @public @deprecated
|
||||
export type IdentityFactoryOptions = {
|
||||
@@ -201,12 +177,10 @@ export const identityServiceFactory: (
|
||||
options?: IdentityFactoryOptions | undefined,
|
||||
) => ServiceFactory<IdentityService, 'plugin'>;
|
||||
|
||||
// @public
|
||||
export interface LifecycleMiddlewareOptions {
|
||||
// (undocumented)
|
||||
lifecycle: LifecycleService;
|
||||
startupRequestPauseTimeout?: HumanDuration;
|
||||
}
|
||||
// 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: () => ServiceFactory<
|
||||
@@ -214,7 +188,7 @@ export const lifecycleServiceFactory: () => ServiceFactory<
|
||||
'plugin'
|
||||
>;
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export function loadBackendConfig(options: {
|
||||
remote?: LoadConfigOptionsRemote;
|
||||
argv: string[];
|
||||
@@ -224,36 +198,26 @@ export function loadBackendConfig(options: {
|
||||
config: Config;
|
||||
}>;
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export const loggerServiceFactory: () => ServiceFactory<
|
||||
LoggerService,
|
||||
'plugin'
|
||||
>;
|
||||
|
||||
// @public
|
||||
export class MiddlewareFactory {
|
||||
compression(): RequestHandler;
|
||||
cors(): RequestHandler;
|
||||
static create(options: MiddlewareFactoryOptions): MiddlewareFactory;
|
||||
error(options?: MiddlewareFactoryErrorOptions): ErrorRequestHandler;
|
||||
helmet(): RequestHandler;
|
||||
logging(): RequestHandler;
|
||||
notFound(): RequestHandler;
|
||||
}
|
||||
// Warning: (ae-forgotten-export) The symbol "MiddlewareFactory_2" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public @deprecated (undocumented)
|
||||
export const MiddlewareFactory: typeof MiddlewareFactory_2;
|
||||
|
||||
// @public
|
||||
export interface MiddlewareFactoryErrorOptions {
|
||||
logAllErrors?: boolean;
|
||||
showStackTraces?: boolean;
|
||||
}
|
||||
// Warning: (ae-forgotten-export) The symbol "MiddlewareFactoryErrorOptions_2" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public @deprecated (undocumented)
|
||||
export type MiddlewareFactoryErrorOptions = MiddlewareFactoryErrorOptions_2;
|
||||
|
||||
// @public
|
||||
export interface MiddlewareFactoryOptions {
|
||||
// (undocumented)
|
||||
config: RootConfigService;
|
||||
// (undocumented)
|
||||
logger: LoggerService;
|
||||
}
|
||||
// Warning: (ae-forgotten-export) The symbol "MiddlewareFactoryOptions_2" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public @deprecated (undocumented)
|
||||
export type MiddlewareFactoryOptions = MiddlewareFactoryOptions_2;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const permissionsServiceFactory: () => ServiceFactory<
|
||||
@@ -261,14 +225,20 @@ export const permissionsServiceFactory: () => ServiceFactory<
|
||||
'plugin'
|
||||
>;
|
||||
|
||||
// @public
|
||||
export function readCorsOptions(config?: Config): CorsOptions;
|
||||
// Warning: (ae-forgotten-export) The symbol "readCorsOptions_2" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public @deprecated (undocumented)
|
||||
export const readCorsOptions: typeof readCorsOptions_2;
|
||||
|
||||
// @public
|
||||
export function readHelmetOptions(config?: Config): HelmetOptions;
|
||||
// Warning: (ae-forgotten-export) The symbol "readHelmetOptions_2" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public @deprecated (undocumented)
|
||||
export const readHelmetOptions: typeof readHelmetOptions_2;
|
||||
|
||||
// @public
|
||||
export function readHttpServerOptions(config?: Config): HttpServerOptions;
|
||||
// Warning: (ae-forgotten-export) The symbol "readHttpServerOptions_2" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public @deprecated (undocumented)
|
||||
export const readHttpServerOptions: typeof readHttpServerOptions_2;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export interface RootConfigFactoryOptions {
|
||||
@@ -283,35 +253,19 @@ export const rootConfigServiceFactory: (
|
||||
options?: RootConfigFactoryOptions | undefined,
|
||||
) => ServiceFactory<RootConfigService, 'root'>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RootHttpRouterConfigureContext {
|
||||
// (undocumented)
|
||||
app: Express_2;
|
||||
// (undocumented)
|
||||
applyDefaults: () => void;
|
||||
// (undocumented)
|
||||
config: RootConfigService;
|
||||
// (undocumented)
|
||||
lifecycle: LifecycleService;
|
||||
// (undocumented)
|
||||
logger: LoggerService;
|
||||
// (undocumented)
|
||||
middleware: MiddlewareFactory;
|
||||
// (undocumented)
|
||||
routes: RequestHandler;
|
||||
// (undocumented)
|
||||
server: Server;
|
||||
}
|
||||
// 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;
|
||||
|
||||
// @public
|
||||
export type RootHttpRouterFactoryOptions = {
|
||||
indexPath?: string | false;
|
||||
configure?(context: RootHttpRouterConfigureContext): void;
|
||||
};
|
||||
// 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 (undocumented)
|
||||
// @public @deprecated (undocumented)
|
||||
export const rootHttpRouterServiceFactory: (
|
||||
options?: RootHttpRouterFactoryOptions | undefined,
|
||||
options?: RootHttpRouterFactoryOptions_2 | undefined,
|
||||
) => ServiceFactory<RootHttpRouterService, 'root'>;
|
||||
|
||||
// @public @deprecated
|
||||
@@ -320,7 +274,7 @@ export const rootLifecycleServiceFactory: () => ServiceFactory<
|
||||
'root'
|
||||
>;
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export const rootLoggerServiceFactory: () => ServiceFactory<
|
||||
RootLoggerService,
|
||||
'root'
|
||||
@@ -350,7 +304,7 @@ export const userInfoServiceFactory: () => ServiceFactory<
|
||||
'plugin'
|
||||
>;
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export class WinstonLogger implements RootLoggerService {
|
||||
// (undocumented)
|
||||
addRedactions(redactions: Iterable<string>): void;
|
||||
@@ -372,15 +326,8 @@ export class WinstonLogger implements RootLoggerService {
|
||||
warn(message: string, meta?: JsonObject): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface WinstonLoggerOptions {
|
||||
// (undocumented)
|
||||
format?: Format;
|
||||
// (undocumented)
|
||||
level?: string;
|
||||
// (undocumented)
|
||||
meta?: JsonObject;
|
||||
// (undocumented)
|
||||
transports?: transport[];
|
||||
}
|
||||
// Warning: (ae-forgotten-export) The symbol "WinstonLoggerOptions_2" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public @deprecated (undocumented)
|
||||
export type WinstonLoggerOptions = WinstonLoggerOptions_2;
|
||||
```
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
"winston-transport": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-defaults": "workspace:^",
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@types/compression": "^1.7.0",
|
||||
|
||||
@@ -14,56 +14,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { createConfigSecretEnumerator as _createConfigSecretEnumerator } from '../../../backend-defaults/src/entrypoints/rootConfig/createConfigSecretEnumerator';
|
||||
|
||||
import { resolve as resolvePath } from 'path';
|
||||
import parseArgs from 'minimist';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { findPaths } from '@backstage/cli-common';
|
||||
import {
|
||||
loadConfigSchema,
|
||||
loadConfig,
|
||||
ConfigTarget,
|
||||
LoadConfigOptionsRemote,
|
||||
ConfigSchema,
|
||||
} from '@backstage/config-loader';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import type { Config, AppConfig } from '@backstage/config';
|
||||
import { getPackages } from '@manypkg/get-packages';
|
||||
import { ObservableConfigProxy } from './ObservableConfigProxy';
|
||||
import { isValidUrl } from '../lib/urls';
|
||||
|
||||
/** @public */
|
||||
export async function createConfigSecretEnumerator(options: {
|
||||
logger: LoggerService;
|
||||
dir?: string;
|
||||
schema?: ConfigSchema;
|
||||
}): Promise<(config: Config) => Iterable<string>> {
|
||||
const { logger, dir = process.cwd() } = options;
|
||||
const { packages } = await getPackages(dir);
|
||||
const schema =
|
||||
options.schema ??
|
||||
(await loadConfigSchema({
|
||||
dependencies: packages.map(p => p.packageJson.name),
|
||||
}));
|
||||
|
||||
return (config: Config) => {
|
||||
const [secretsData] = schema.process(
|
||||
[{ data: config.getOptional() ?? {}, context: 'schema-enumerator' }],
|
||||
{
|
||||
visibility: ['secret'],
|
||||
ignoreSchemaErrors: true,
|
||||
},
|
||||
);
|
||||
const secrets = new Set<string>();
|
||||
JSON.parse(
|
||||
JSON.stringify(secretsData.data),
|
||||
(_, v) => typeof v === 'string' && secrets.add(v),
|
||||
);
|
||||
logger.info(
|
||||
`Found ${secrets.size} new secrets in config that will be redacted`,
|
||||
);
|
||||
return secrets;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/rootConfig` instead.
|
||||
*/
|
||||
export const createConfigSecretEnumerator = _createConfigSecretEnumerator;
|
||||
|
||||
/**
|
||||
* Load configuration for a Backend.
|
||||
@@ -71,6 +42,7 @@ export async function createConfigSecretEnumerator(options: {
|
||||
* This function should only be called once, during the initialization of the backend.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Please migrate to the new backend system and use `coreServices.rootConfig` instead, or the {@link @backstage/config-loader#ConfigSources} facilities if required.
|
||||
*/
|
||||
export async function loadBackendConfig(options: {
|
||||
remote?: LoadConfigOptionsRemote;
|
||||
|
||||
@@ -14,17 +14,67 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { readHttpServerOptions } from './config';
|
||||
export { createHttpServer } from './createHttpServer';
|
||||
export { MiddlewareFactory } from './MiddlewareFactory';
|
||||
export type {
|
||||
MiddlewareFactoryErrorOptions,
|
||||
MiddlewareFactoryOptions,
|
||||
} from './MiddlewareFactory';
|
||||
export { readCorsOptions } from './readCorsOptions';
|
||||
export { readHelmetOptions } from './readHelmetOptions';
|
||||
export type {
|
||||
ExtendedHttpServer,
|
||||
HttpServerCertificateOptions,
|
||||
HttpServerOptions,
|
||||
} from './types';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import {
|
||||
readHttpServerOptions as _readHttpServerOptions,
|
||||
createHttpServer as _createHttpServer,
|
||||
MiddlewareFactory as _MiddlewareFactory,
|
||||
readCorsOptions as _readCorsOptions,
|
||||
readHelmetOptions as _readHelmetOptions,
|
||||
type MiddlewareFactoryErrorOptions as _MiddlewareFactoryErrorOptions,
|
||||
type MiddlewareFactoryOptions as _MiddlewareFactoryOptions,
|
||||
type ExtendedHttpServer as _ExtendedHttpServer,
|
||||
type HttpServerCertificateOptions as _HttpServerCertificateOptions,
|
||||
type HttpServerOptions as _HttpServerOptions,
|
||||
} from '../../../backend-defaults/src/entrypoints/rootHttpRouter/http';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @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.
|
||||
*/
|
||||
export const readCorsOptions = _readCorsOptions;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
|
||||
*/
|
||||
export const readHelmetOptions = _readHelmetOptions;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
|
||||
*/
|
||||
export const MiddlewareFactory = _MiddlewareFactory;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
|
||||
*/
|
||||
export type MiddlewareFactoryErrorOptions = _MiddlewareFactoryErrorOptions;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
|
||||
*/
|
||||
export type MiddlewareFactoryOptions = _MiddlewareFactoryOptions;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
|
||||
*/
|
||||
export type ExtendedHttpServer = _ExtendedHttpServer;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
|
||||
*/
|
||||
export type HttpServerCertificateOptions = _HttpServerCertificateOptions;
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
|
||||
*/
|
||||
export type HttpServerOptions = _HttpServerOptions;
|
||||
|
||||
@@ -14,65 +14,37 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import {
|
||||
WinstonLogger as _WinstonLogger,
|
||||
type WinstonLoggerOptions as _WinstonLoggerOptions,
|
||||
} from '../../../backend-defaults/src/entrypoints/rootLogger';
|
||||
|
||||
import {
|
||||
LoggerService,
|
||||
RootLoggerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { Format, TransformableInfo } from 'logform';
|
||||
import {
|
||||
Logger,
|
||||
format,
|
||||
createLogger,
|
||||
transports,
|
||||
transport as Transport,
|
||||
} from 'winston';
|
||||
import { MESSAGE } from 'triple-beam';
|
||||
import { escapeRegExp } from '../lib/escapeRegExp';
|
||||
import { Format } from 'logform';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/rootLogger` instead.
|
||||
*/
|
||||
export interface WinstonLoggerOptions {
|
||||
meta?: JsonObject;
|
||||
level?: string;
|
||||
format?: Format;
|
||||
transports?: Transport[];
|
||||
}
|
||||
export type WinstonLoggerOptions = _WinstonLoggerOptions;
|
||||
|
||||
/**
|
||||
* A {@link @backstage/backend-plugin-api#LoggerService} implementation based on winston.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/rootLogger` instead.
|
||||
*/
|
||||
export class WinstonLogger implements RootLoggerService {
|
||||
#winston: Logger;
|
||||
#addRedactions?: (redactions: Iterable<string>) => void;
|
||||
|
||||
/**
|
||||
* Creates a {@link WinstonLogger} instance.
|
||||
*/
|
||||
static create(options: WinstonLoggerOptions): WinstonLogger {
|
||||
const redacter = WinstonLogger.redacter();
|
||||
const defaultFormatter =
|
||||
process.env.NODE_ENV === 'production'
|
||||
? format.json()
|
||||
: WinstonLogger.colorFormat();
|
||||
|
||||
let logger = createLogger({
|
||||
level: process.env.LOG_LEVEL || options.level || 'info',
|
||||
format: format.combine(
|
||||
options.format ?? defaultFormatter,
|
||||
redacter.format,
|
||||
),
|
||||
transports: options.transports ?? new transports.Console(),
|
||||
});
|
||||
|
||||
if (options.meta) {
|
||||
logger = logger.child(options.meta);
|
||||
}
|
||||
|
||||
return new WinstonLogger(logger, redacter.add);
|
||||
return new WinstonLogger(_WinstonLogger.create(options));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,111 +54,39 @@ export class WinstonLogger implements RootLoggerService {
|
||||
format: Format;
|
||||
add: (redactions: Iterable<string>) => void;
|
||||
} {
|
||||
const redactionSet = new Set<string>();
|
||||
|
||||
let redactionPattern: RegExp | undefined = undefined;
|
||||
|
||||
return {
|
||||
format: format((obj: TransformableInfo) => {
|
||||
if (!redactionPattern || !obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
obj[MESSAGE] = obj[MESSAGE]?.replace?.(redactionPattern, '***');
|
||||
|
||||
return obj;
|
||||
})(),
|
||||
add(newRedactions) {
|
||||
let added = 0;
|
||||
for (const redactionToTrim of newRedactions) {
|
||||
// Trimming the string ensures that we don't accdentally get extra
|
||||
// newlines or other whitespace interfering with the redaction; this
|
||||
// can happen for example when using string literals in yaml
|
||||
const redaction = redactionToTrim.trim();
|
||||
// Exclude secrets that are empty or just one character in length. These
|
||||
// typically mean that you are running local dev or tests, or using the
|
||||
// --lax flag which sets things to just 'x'.
|
||||
if (redaction.length <= 1) {
|
||||
continue;
|
||||
}
|
||||
if (!redactionSet.has(redaction)) {
|
||||
redactionSet.add(redaction);
|
||||
added += 1;
|
||||
}
|
||||
}
|
||||
if (added > 0) {
|
||||
const redactions = Array.from(redactionSet)
|
||||
.map(r => escapeRegExp(r))
|
||||
.join('|');
|
||||
redactionPattern = new RegExp(`(${redactions})`, 'g');
|
||||
}
|
||||
},
|
||||
};
|
||||
return _WinstonLogger.redacter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pretty printed winston log formatter.
|
||||
*/
|
||||
static colorFormat(): Format {
|
||||
const colorizer = format.colorize();
|
||||
|
||||
return format.combine(
|
||||
format.timestamp(),
|
||||
format.colorize({
|
||||
colors: {
|
||||
timestamp: 'dim',
|
||||
prefix: 'blue',
|
||||
field: 'cyan',
|
||||
debug: 'grey',
|
||||
},
|
||||
}),
|
||||
format.printf((info: TransformableInfo) => {
|
||||
const { timestamp, level, message, plugin, service, ...fields } = info;
|
||||
const prefix = plugin || service;
|
||||
const timestampColor = colorizer.colorize('timestamp', timestamp);
|
||||
const prefixColor = colorizer.colorize('prefix', prefix);
|
||||
|
||||
const extraFields = Object.entries(fields)
|
||||
.map(
|
||||
([key, value]) =>
|
||||
`${colorizer.colorize('field', `${key}`)}=${value}`,
|
||||
)
|
||||
.join(' ');
|
||||
|
||||
return `${timestampColor} ${prefixColor} ${level} ${message} ${extraFields}`;
|
||||
}),
|
||||
);
|
||||
return _WinstonLogger.colorFormat();
|
||||
}
|
||||
|
||||
private constructor(
|
||||
winston: Logger,
|
||||
addRedactions?: (redactions: Iterable<string>) => void,
|
||||
) {
|
||||
this.#winston = winston;
|
||||
this.#addRedactions = addRedactions;
|
||||
}
|
||||
private constructor(private readonly impl: _WinstonLogger) {}
|
||||
|
||||
error(message: string, meta?: JsonObject): void {
|
||||
this.#winston.error(message, meta);
|
||||
this.impl.error(message, meta);
|
||||
}
|
||||
|
||||
warn(message: string, meta?: JsonObject): void {
|
||||
this.#winston.warn(message, meta);
|
||||
this.impl.warn(message, meta);
|
||||
}
|
||||
|
||||
info(message: string, meta?: JsonObject): void {
|
||||
this.#winston.info(message, meta);
|
||||
this.impl.info(message, meta);
|
||||
}
|
||||
|
||||
debug(message: string, meta?: JsonObject): void {
|
||||
this.#winston.debug(message, meta);
|
||||
this.impl.debug(message, meta);
|
||||
}
|
||||
|
||||
child(meta: JsonObject): LoggerService {
|
||||
return new WinstonLogger(this.#winston.child(meta));
|
||||
return this.impl.child(meta);
|
||||
}
|
||||
|
||||
addRedactions(redactions: Iterable<string>) {
|
||||
this.#addRedactions?.(redactions);
|
||||
this.impl.addRedactions(redactions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,5 +16,9 @@
|
||||
|
||||
export * from './auth';
|
||||
export * from './httpAuth';
|
||||
export * from './httpRouter';
|
||||
export * from './logger';
|
||||
export * from './rootHttpRouter';
|
||||
export * from './rootLogger';
|
||||
export * from './scheduler';
|
||||
export * from './userInfo';
|
||||
|
||||
@@ -15,10 +15,9 @@
|
||||
*/
|
||||
|
||||
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 };
|
||||
// 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
|
||||
@@ -57,77 +56,16 @@ export class HostDiscovery implements DiscoveryService {
|
||||
* 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'),
|
||||
);
|
||||
return new HostDiscovery(_HostDiscovery.fromConfig(config, options));
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
private constructor(private readonly impl: _HostDiscovery) {}
|
||||
|
||||
async getBaseUrl(pluginId: string): Promise<string> {
|
||||
return this.getTargetFromConfig(pluginId, 'internal');
|
||||
return this.impl.getBaseUrl(pluginId);
|
||||
}
|
||||
|
||||
async getExternalBaseUrl(pluginId: string): Promise<string> {
|
||||
return this.getTargetFromConfig(pluginId, 'external');
|
||||
return this.impl.getExternalBaseUrl(pluginId);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-70
@@ -14,26 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { LifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { ServiceUnavailableError } from '@backstage/errors';
|
||||
import { HumanDuration, durationToMilliseconds } from '@backstage/types';
|
||||
import { RequestHandler } from 'express';
|
||||
|
||||
export const DEFAULT_TIMEOUT = { seconds: 5 };
|
||||
// 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 interface LifecycleMiddlewareOptions {
|
||||
lifecycle: LifecycleService;
|
||||
/**
|
||||
* The maximum time that paused requests will wait for the service to start, before returning an error.
|
||||
*
|
||||
* Defaults to 5 seconds.
|
||||
*/
|
||||
startupRequestPauseTimeout?: HumanDuration;
|
||||
}
|
||||
export type LifecycleMiddlewareOptions = _LifecycleMiddlewareOptions;
|
||||
|
||||
/**
|
||||
* Creates a middleware that pauses requests until the service has started.
|
||||
@@ -48,59 +40,6 @@ export interface LifecycleMiddlewareOptions {
|
||||
* {@link @backstage/errors#ServiceUnavailableError}.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/httpRouter` instead.
|
||||
*/
|
||||
export function createLifecycleMiddleware(
|
||||
options: LifecycleMiddlewareOptions,
|
||||
): RequestHandler {
|
||||
const { lifecycle, startupRequestPauseTimeout = DEFAULT_TIMEOUT } = options;
|
||||
|
||||
let state: 'init' | 'up' | 'down' = 'init';
|
||||
const waiting = new Set<{
|
||||
next: (err?: Error) => void;
|
||||
timeout: NodeJS.Timeout;
|
||||
}>();
|
||||
|
||||
lifecycle.addStartupHook(async () => {
|
||||
if (state === 'init') {
|
||||
state = 'up';
|
||||
for (const item of waiting) {
|
||||
clearTimeout(item.timeout);
|
||||
item.next();
|
||||
}
|
||||
waiting.clear();
|
||||
}
|
||||
});
|
||||
|
||||
lifecycle.addShutdownHook(async () => {
|
||||
state = 'down';
|
||||
|
||||
for (const item of waiting) {
|
||||
clearTimeout(item.timeout);
|
||||
item.next(new ServiceUnavailableError('Service is shutting down'));
|
||||
}
|
||||
waiting.clear();
|
||||
});
|
||||
|
||||
const timeoutMs = durationToMilliseconds(startupRequestPauseTimeout);
|
||||
|
||||
return (_req, _res, next) => {
|
||||
if (state === 'up') {
|
||||
next();
|
||||
return;
|
||||
} else if (state === 'down') {
|
||||
next(new ServiceUnavailableError('Service is shutting down'));
|
||||
return;
|
||||
}
|
||||
|
||||
const item = {
|
||||
next,
|
||||
timeout: setTimeout(() => {
|
||||
if (waiting.delete(item)) {
|
||||
next(new ServiceUnavailableError('Service has not started up yet'));
|
||||
}
|
||||
}, timeoutMs),
|
||||
};
|
||||
|
||||
waiting.add(item);
|
||||
};
|
||||
}
|
||||
export const createLifecycleMiddleware = _createLifecycleMiddleware;
|
||||
|
||||
+8
-70
@@ -14,27 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Handler } from 'express';
|
||||
import PromiseRouter from 'express-promise-router';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import {
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
HttpRouterServiceAuthPolicy,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { createLifecycleMiddleware } from './createLifecycleMiddleware';
|
||||
import { createCredentialsBarrier } from './createCredentialsBarrier';
|
||||
import { createAuthIntegrationRouter } from './createAuthIntegrationRouter';
|
||||
import { createCookieAuthRefreshMiddleware } from './createCookieAuthRefreshMiddleware';
|
||||
httpRouterServiceFactory as _httpRouterServiceFactory,
|
||||
type HttpRouterFactoryOptions as _HttpRouterFactoryOptions,
|
||||
} from '../../../../../backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory';
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/httpRouter` instead.
|
||||
*/
|
||||
export interface HttpRouterFactoryOptions {
|
||||
/**
|
||||
* A callback used to generate the path for each plugin, defaults to `/api/{pluginId}`.
|
||||
*/
|
||||
getPath?(pluginId: string): string;
|
||||
}
|
||||
export type HttpRouterFactoryOptions = _HttpRouterFactoryOptions;
|
||||
|
||||
/**
|
||||
* HTTP route registration for plugins.
|
||||
@@ -44,58 +34,6 @@ export interface HttpRouterFactoryOptions {
|
||||
* for more information.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/httpRouter` instead.
|
||||
*/
|
||||
export const httpRouterServiceFactory = createServiceFactory(
|
||||
(options?: HttpRouterFactoryOptions) => ({
|
||||
service: coreServices.httpRouter,
|
||||
initialization: 'always',
|
||||
deps: {
|
||||
plugin: coreServices.pluginMetadata,
|
||||
config: coreServices.rootConfig,
|
||||
logger: coreServices.logger,
|
||||
lifecycle: coreServices.lifecycle,
|
||||
rootHttpRouter: coreServices.rootHttpRouter,
|
||||
auth: coreServices.auth,
|
||||
httpAuth: coreServices.httpAuth,
|
||||
},
|
||||
async factory({
|
||||
auth,
|
||||
httpAuth,
|
||||
config,
|
||||
logger,
|
||||
plugin,
|
||||
rootHttpRouter,
|
||||
lifecycle,
|
||||
}) {
|
||||
if (options?.getPath) {
|
||||
logger.warn(
|
||||
`DEPRECATION WARNING: The 'getPath' option for HttpRouterService is deprecated. The ability to reconfigure the '/api/' path prefix for plugins will be removed in the future.`,
|
||||
);
|
||||
}
|
||||
const getPath = options?.getPath ?? (id => `/api/${id}`);
|
||||
const path = getPath(plugin.getId());
|
||||
|
||||
const router = PromiseRouter();
|
||||
rootHttpRouter.use(path, router);
|
||||
|
||||
const credentialsBarrier = createCredentialsBarrier({
|
||||
httpAuth,
|
||||
config,
|
||||
});
|
||||
|
||||
router.use(createAuthIntegrationRouter({ auth }));
|
||||
router.use(createLifecycleMiddleware({ lifecycle }));
|
||||
router.use(credentialsBarrier.middleware);
|
||||
router.use(createCookieAuthRefreshMiddleware({ auth, httpAuth }));
|
||||
|
||||
return {
|
||||
use(handler: Handler): void {
|
||||
router.use(handler);
|
||||
},
|
||||
addAuthPolicy(policy: HttpRouterServiceAuthPolicy): void {
|
||||
credentialsBarrier.addAuthPolicy(policy);
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
);
|
||||
export const httpRouterServiceFactory = _httpRouterServiceFactory;
|
||||
|
||||
@@ -18,14 +18,10 @@ export * from './cache';
|
||||
export * from './config';
|
||||
export * from './database';
|
||||
export * from './discovery';
|
||||
export * from './httpRouter';
|
||||
export * from './identity';
|
||||
export * from './lifecycle';
|
||||
export * from './logger';
|
||||
export * from './permissions';
|
||||
export * from './rootHttpRouter';
|
||||
export * from './rootLifecycle';
|
||||
export * from './rootLogger';
|
||||
export * from './tokenManager';
|
||||
export * from './urlReader';
|
||||
|
||||
|
||||
+4
-14
@@ -14,10 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
createServiceFactory,
|
||||
coreServices,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { loggerServiceFactory as _loggerServiceFactory } from '../../../../../backend-defaults/src/entrypoints/logger/loggerServiceFactory';
|
||||
|
||||
/**
|
||||
* Plugin-level logging.
|
||||
@@ -27,14 +25,6 @@ import {
|
||||
* for more information.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/logger` instead.
|
||||
*/
|
||||
export const loggerServiceFactory = createServiceFactory({
|
||||
service: coreServices.logger,
|
||||
deps: {
|
||||
rootLogger: coreServices.rootLogger,
|
||||
plugin: coreServices.pluginMetadata,
|
||||
},
|
||||
factory({ rootLogger, plugin }) {
|
||||
return rootLogger.child({ plugin: plugin.getId() });
|
||||
},
|
||||
});
|
||||
export const loggerServiceFactory = _loggerServiceFactory;
|
||||
|
||||
+13
-74
@@ -15,101 +15,40 @@
|
||||
*/
|
||||
|
||||
import { RootHttpRouterService } from '@backstage/backend-plugin-api';
|
||||
import { Handler, Router } from 'express';
|
||||
import trimEnd from 'lodash/trimEnd';
|
||||
|
||||
function normalizePath(path: string): string {
|
||||
return `${trimEnd(path, '/')}/`;
|
||||
}
|
||||
// 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 interface DefaultRootHttpRouterOptions {
|
||||
/**
|
||||
* The path to forward all unmatched requests to. Defaults to '/api/app' if
|
||||
* not given. Disables index path behavior if false is given.
|
||||
*/
|
||||
indexPath?: string | false;
|
||||
}
|
||||
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 {
|
||||
#indexPath?: string;
|
||||
|
||||
#router = Router();
|
||||
#namedRoutes = Router();
|
||||
#indexRouter = Router();
|
||||
#existingPaths = new Array<string>();
|
||||
|
||||
static create(options?: DefaultRootHttpRouterOptions) {
|
||||
let indexPath;
|
||||
if (options?.indexPath === false) {
|
||||
indexPath = undefined;
|
||||
} else if (options?.indexPath === undefined) {
|
||||
indexPath = '/api/app';
|
||||
} else if (options?.indexPath === '') {
|
||||
throw new Error('indexPath option may not be an empty string');
|
||||
} else {
|
||||
indexPath = options.indexPath;
|
||||
}
|
||||
return new DefaultRootHttpRouter(indexPath);
|
||||
return new DefaultRootHttpRouter(_DefaultRootHttpRouter.create(options));
|
||||
}
|
||||
|
||||
private constructor(indexPath?: string) {
|
||||
this.#indexPath = indexPath;
|
||||
this.#router.use(this.#namedRoutes);
|
||||
|
||||
// Any request with a /api/ prefix will skip the index router, even if no named router matches
|
||||
this.#router.use('/api/', (_req, _res, next) => {
|
||||
next('router');
|
||||
});
|
||||
|
||||
if (this.#indexPath) {
|
||||
this.#router.use(this.#indexRouter);
|
||||
}
|
||||
}
|
||||
private constructor(private readonly impl: RootHttpRouterService) {}
|
||||
|
||||
use(path: string, handler: Handler) {
|
||||
if (path.match(/^[/\s]*$/)) {
|
||||
throw new Error(`Root router path may not be empty`);
|
||||
}
|
||||
const conflictingPath = this.#findConflictingPath(path);
|
||||
if (conflictingPath) {
|
||||
throw new Error(
|
||||
`Path ${path} conflicts with the existing path ${conflictingPath}`,
|
||||
);
|
||||
}
|
||||
this.#existingPaths.push(path);
|
||||
this.#namedRoutes.use(path, handler);
|
||||
|
||||
if (this.#indexPath === path) {
|
||||
this.#indexRouter.use(handler);
|
||||
}
|
||||
this.impl.use(path, handler);
|
||||
}
|
||||
|
||||
handler(): Handler {
|
||||
return this.#router;
|
||||
}
|
||||
|
||||
#findConflictingPath(newPath: string): string | undefined {
|
||||
const normalizedNewPath = normalizePath(newPath);
|
||||
for (const path of this.#existingPaths) {
|
||||
const normalizedPath = normalizePath(path);
|
||||
if (normalizedPath.startsWith(normalizedNewPath)) {
|
||||
return path;
|
||||
}
|
||||
if (normalizedNewPath.startsWith(normalizedPath)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
return (this.impl as any).handler();
|
||||
}
|
||||
}
|
||||
|
||||
+14
-87
@@ -14,35 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import {
|
||||
RootConfigService,
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
LifecycleService,
|
||||
LoggerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import express, { RequestHandler, Express } from 'express';
|
||||
import type { Server } from 'node:http';
|
||||
import {
|
||||
createHttpServer,
|
||||
MiddlewareFactory,
|
||||
readHttpServerOptions,
|
||||
} from '../../../http';
|
||||
import { DefaultRootHttpRouter } from './DefaultRootHttpRouter';
|
||||
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 interface RootHttpRouterConfigureContext {
|
||||
app: Express;
|
||||
server: Server;
|
||||
middleware: MiddlewareFactory;
|
||||
routes: RequestHandler;
|
||||
config: RootConfigService;
|
||||
logger: LoggerService;
|
||||
lifecycle: LifecycleService;
|
||||
applyDefaults: () => void;
|
||||
}
|
||||
export type RootHttpRouterConfigureContext = _RootHttpRouterConfigureContext;
|
||||
|
||||
/**
|
||||
* HTTP route registration for root services.
|
||||
@@ -52,68 +35,12 @@ export interface RootHttpRouterConfigureContext {
|
||||
* for more information.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
|
||||
*/
|
||||
export type RootHttpRouterFactoryOptions = {
|
||||
/**
|
||||
* The path to forward all unmatched requests to. Defaults to '/api/app' if
|
||||
* not given. Disables index path behavior if false is given.
|
||||
*/
|
||||
indexPath?: string | false;
|
||||
export type RootHttpRouterFactoryOptions = _RootHttpRouterFactoryOptions;
|
||||
|
||||
configure?(context: RootHttpRouterConfigureContext): void;
|
||||
};
|
||||
|
||||
function defaultConfigure({ applyDefaults }: RootHttpRouterConfigureContext) {
|
||||
applyDefaults();
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const rootHttpRouterServiceFactory = createServiceFactory(
|
||||
(options?: RootHttpRouterFactoryOptions) => ({
|
||||
service: coreServices.rootHttpRouter,
|
||||
deps: {
|
||||
config: coreServices.rootConfig,
|
||||
rootLogger: coreServices.rootLogger,
|
||||
lifecycle: coreServices.rootLifecycle,
|
||||
},
|
||||
async factory({ config, rootLogger, lifecycle }) {
|
||||
const { indexPath, configure = defaultConfigure } = options ?? {};
|
||||
const logger = rootLogger.child({ service: 'rootHttpRouter' });
|
||||
const app = express();
|
||||
|
||||
const router = DefaultRootHttpRouter.create({ indexPath });
|
||||
const middleware = MiddlewareFactory.create({ config, logger });
|
||||
const routes = router.handler();
|
||||
const server = await createHttpServer(
|
||||
app,
|
||||
readHttpServerOptions(config.getOptionalConfig('backend')),
|
||||
{ logger },
|
||||
);
|
||||
|
||||
configure({
|
||||
app,
|
||||
server,
|
||||
routes,
|
||||
middleware,
|
||||
config,
|
||||
logger,
|
||||
lifecycle,
|
||||
applyDefaults() {
|
||||
app.use(middleware.helmet());
|
||||
app.use(middleware.cors());
|
||||
app.use(middleware.compression());
|
||||
app.use(middleware.logging());
|
||||
app.use(routes);
|
||||
app.use(middleware.notFound());
|
||||
app.use(middleware.error());
|
||||
},
|
||||
});
|
||||
|
||||
lifecycle.addShutdownHook(() => server.stop());
|
||||
|
||||
await server.start();
|
||||
|
||||
return router;
|
||||
},
|
||||
}),
|
||||
);
|
||||
/**
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead.
|
||||
*/
|
||||
export const rootHttpRouterServiceFactory = _rootHttpRouterServiceFactory;
|
||||
|
||||
+4
-32
@@ -14,13 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
createServiceFactory,
|
||||
coreServices,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { WinstonLogger } from '../../../logging';
|
||||
import { transports, format } from 'winston';
|
||||
import { createConfigSecretEnumerator } from '../../../config';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { rootLoggerServiceFactory as _rootLoggerServiceFactory } from '../../../../../backend-defaults/src/entrypoints/rootLogger/rootLoggerServiceFactory';
|
||||
|
||||
/**
|
||||
* Root-level logging.
|
||||
@@ -30,29 +25,6 @@ import { createConfigSecretEnumerator } from '../../../config';
|
||||
* for more information.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Please import from `@backstage/backend-defaults/rootLogger` instead.
|
||||
*/
|
||||
export const rootLoggerServiceFactory = createServiceFactory({
|
||||
service: coreServices.rootLogger,
|
||||
deps: {
|
||||
config: coreServices.rootConfig,
|
||||
},
|
||||
async factory({ config }) {
|
||||
const logger = WinstonLogger.create({
|
||||
meta: {
|
||||
service: 'backstage',
|
||||
},
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format:
|
||||
process.env.NODE_ENV === 'production'
|
||||
? format.json()
|
||||
: WinstonLogger.colorFormat(),
|
||||
transports: [new transports.Console()],
|
||||
});
|
||||
|
||||
const secretEnumerator = await createConfigSecretEnumerator({ logger });
|
||||
logger.addRedactions(secretEnumerator(config));
|
||||
config.subscribe?.(() => logger.addRedactions(secretEnumerator(config)));
|
||||
|
||||
return logger;
|
||||
},
|
||||
});
|
||||
export const rootLoggerServiceFactory = _rootLoggerServiceFactory;
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle';
|
||||
import { lifecycleServiceFactory } from '@backstage/backend-defaults/lifecycle';
|
||||
import { loggerServiceFactory } from '@backstage/backend-defaults/logger';
|
||||
import {
|
||||
createServiceRef,
|
||||
createServiceFactory,
|
||||
@@ -24,12 +27,6 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { BackendInitializer } from './BackendInitializer';
|
||||
|
||||
import {
|
||||
lifecycleServiceFactory,
|
||||
loggerServiceFactory,
|
||||
rootLifecycleServiceFactory,
|
||||
} from '../services/implementations';
|
||||
|
||||
class MockLogger {
|
||||
debug() {}
|
||||
info() {}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { ErrorRequestHandler } from 'express';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { MiddlewareFactory } from '../../../../backend-app-api/src/http/MiddlewareFactory';
|
||||
import { MiddlewareFactory } from '../../../../backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory';
|
||||
import { getRootLogger } from '../logging';
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { MiddlewareFactory } from '../../../../backend-app-api/src/http/MiddlewareFactory';
|
||||
import { MiddlewareFactory } from '../../../../backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { RequestHandler } from 'express';
|
||||
import { getRootLogger } from '../logging';
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
|
||||
import { MiddlewareFactory } from '../../../../backend-app-api/src/http/MiddlewareFactory';
|
||||
import { MiddlewareFactory } from '../../../../backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory';
|
||||
import { RequestHandler } from 'express';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
readHttpServerOptions,
|
||||
HttpServerOptions,
|
||||
createHttpServer,
|
||||
} from '../../../../../backend-app-api/src/http';
|
||||
} from '../../../../../backend-defaults/src/entrypoints/rootHttpRouter/http';
|
||||
|
||||
export type CspOptions = Record<string, string[]>;
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
## API Report File for "@backstage/backend-defaults"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { HttpRouterService } from '@backstage/backend-plugin-api';
|
||||
import { HumanDuration } from '@backstage/types';
|
||||
import { LifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { RequestHandler } from 'express';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
|
||||
// @public
|
||||
export function createLifecycleMiddleware(
|
||||
options: LifecycleMiddlewareOptions,
|
||||
): RequestHandler;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface HttpRouterFactoryOptions {
|
||||
getPath?(pluginId: string): string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const httpRouterServiceFactory: (
|
||||
options?: HttpRouterFactoryOptions | undefined,
|
||||
) => ServiceFactory<HttpRouterService, 'plugin'>;
|
||||
|
||||
// @public
|
||||
export interface LifecycleMiddlewareOptions {
|
||||
// (undocumented)
|
||||
lifecycle: LifecycleService;
|
||||
startupRequestPauseTimeout?: HumanDuration;
|
||||
}
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
## API Report File for "@backstage/backend-defaults"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
|
||||
// @public
|
||||
export const loggerServiceFactory: () => ServiceFactory<
|
||||
LoggerService,
|
||||
'plugin'
|
||||
>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -3,10 +3,20 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import type { Config } from '@backstage/config';
|
||||
import { ConfigSchema } from '@backstage/config-loader';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { RemoteConfigSourceOptions } from '@backstage/config-loader';
|
||||
import { RootConfigService } from '@backstage/backend-plugin-api';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
|
||||
// @public (undocumented)
|
||||
export function createConfigSecretEnumerator(options: {
|
||||
logger: LoggerService;
|
||||
dir?: string;
|
||||
schema?: ConfigSchema;
|
||||
}): Promise<(config: Config) => Iterable<string>>;
|
||||
|
||||
// @public
|
||||
export interface RootConfigFactoryOptions {
|
||||
argv?: string[];
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
## API Report File for "@backstage/backend-defaults"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
/// <reference types="node" />
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { CorsOptions } from 'cors';
|
||||
import { ErrorRequestHandler } from 'express';
|
||||
import { Express as Express_2 } from 'express';
|
||||
import { Handler } from 'express';
|
||||
import { HelmetOptions } from 'helmet';
|
||||
import * as http from 'http';
|
||||
import { LifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { RequestHandler } from 'express';
|
||||
import { RequestListener } from 'http';
|
||||
import { RootConfigService } from '@backstage/backend-plugin-api';
|
||||
import { RootHttpRouterService } from '@backstage/backend-plugin-api';
|
||||
import type { Server } from 'node:http';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
|
||||
// @public
|
||||
export function createHttpServer(
|
||||
listener: RequestListener,
|
||||
options: HttpServerOptions,
|
||||
deps: {
|
||||
logger: LoggerService;
|
||||
},
|
||||
): Promise<ExtendedHttpServer>;
|
||||
|
||||
// @public
|
||||
export class DefaultRootHttpRouter implements RootHttpRouterService {
|
||||
// (undocumented)
|
||||
static create(options?: DefaultRootHttpRouterOptions): DefaultRootHttpRouter;
|
||||
// (undocumented)
|
||||
handler(): Handler;
|
||||
// (undocumented)
|
||||
use(path: string, handler: Handler): void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface DefaultRootHttpRouterOptions {
|
||||
indexPath?: string | false;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface ExtendedHttpServer extends http.Server {
|
||||
// (undocumented)
|
||||
port(): number;
|
||||
// (undocumented)
|
||||
start(): Promise<void>;
|
||||
// (undocumented)
|
||||
stop(): Promise<void>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type HttpServerCertificateOptions =
|
||||
| {
|
||||
type: 'pem';
|
||||
key: string;
|
||||
cert: string;
|
||||
}
|
||||
| {
|
||||
type: 'generated';
|
||||
hostname: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type HttpServerOptions = {
|
||||
listen: {
|
||||
port: number;
|
||||
host: string;
|
||||
};
|
||||
https?: {
|
||||
certificate: HttpServerCertificateOptions;
|
||||
};
|
||||
};
|
||||
|
||||
// @public
|
||||
export class MiddlewareFactory {
|
||||
compression(): RequestHandler;
|
||||
cors(): RequestHandler;
|
||||
static create(options: MiddlewareFactoryOptions): MiddlewareFactory;
|
||||
error(options?: MiddlewareFactoryErrorOptions): ErrorRequestHandler;
|
||||
helmet(): RequestHandler;
|
||||
logging(): RequestHandler;
|
||||
notFound(): RequestHandler;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface MiddlewareFactoryErrorOptions {
|
||||
logAllErrors?: boolean;
|
||||
showStackTraces?: boolean;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface MiddlewareFactoryOptions {
|
||||
// (undocumented)
|
||||
config: RootConfigService;
|
||||
// (undocumented)
|
||||
logger: LoggerService;
|
||||
}
|
||||
|
||||
// @public
|
||||
export function readCorsOptions(config?: Config): CorsOptions;
|
||||
|
||||
// @public
|
||||
export function readHelmetOptions(config?: Config): HelmetOptions;
|
||||
|
||||
// @public
|
||||
export function readHttpServerOptions(config?: Config): HttpServerOptions;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RootHttpRouterConfigureContext {
|
||||
// (undocumented)
|
||||
app: Express_2;
|
||||
// (undocumented)
|
||||
applyDefaults: () => void;
|
||||
// (undocumented)
|
||||
config: RootConfigService;
|
||||
// (undocumented)
|
||||
lifecycle: LifecycleService;
|
||||
// (undocumented)
|
||||
logger: LoggerService;
|
||||
// (undocumented)
|
||||
middleware: MiddlewareFactory;
|
||||
// (undocumented)
|
||||
routes: RequestHandler;
|
||||
// (undocumented)
|
||||
server: Server;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type RootHttpRouterFactoryOptions = {
|
||||
indexPath?: string | false;
|
||||
configure?(context: RootHttpRouterConfigureContext): void;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export const rootHttpRouterServiceFactory: (
|
||||
options?: RootHttpRouterFactoryOptions | undefined,
|
||||
) => ServiceFactory<RootHttpRouterService, 'root'>;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,54 @@
|
||||
## API Report File for "@backstage/backend-defaults"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { Format } from 'logform';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { RootLoggerService } from '@backstage/backend-plugin-api';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
import { transport } from 'winston';
|
||||
|
||||
// @public
|
||||
export const rootLoggerServiceFactory: () => ServiceFactory<
|
||||
RootLoggerService,
|
||||
'root'
|
||||
>;
|
||||
|
||||
// @public
|
||||
export class WinstonLogger implements RootLoggerService {
|
||||
// (undocumented)
|
||||
addRedactions(redactions: Iterable<string>): void;
|
||||
// (undocumented)
|
||||
child(meta: JsonObject): LoggerService;
|
||||
static colorFormat(): Format;
|
||||
static create(options: WinstonLoggerOptions): WinstonLogger;
|
||||
// (undocumented)
|
||||
debug(message: string, meta?: JsonObject): void;
|
||||
// (undocumented)
|
||||
error(message: string, meta?: JsonObject): void;
|
||||
// (undocumented)
|
||||
info(message: string, meta?: JsonObject): void;
|
||||
static redacter(): {
|
||||
format: Format;
|
||||
add: (redactions: Iterable<string>) => void;
|
||||
};
|
||||
// (undocumented)
|
||||
warn(message: string, meta?: JsonObject): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface WinstonLoggerOptions {
|
||||
// (undocumented)
|
||||
format?: Format;
|
||||
// (undocumented)
|
||||
level?: string;
|
||||
// (undocumented)
|
||||
meta?: JsonObject;
|
||||
// (undocumented)
|
||||
transports?: transport[];
|
||||
}
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -25,10 +25,14 @@
|
||||
"./database": "./src/entrypoints/database/index.ts",
|
||||
"./discovery": "./src/entrypoints/discovery/index.ts",
|
||||
"./httpAuth": "./src/entrypoints/httpAuth/index.ts",
|
||||
"./httpRouter": "./src/entrypoints/httpRouter/index.ts",
|
||||
"./lifecycle": "./src/entrypoints/lifecycle/index.ts",
|
||||
"./logger": "./src/entrypoints/logger/index.ts",
|
||||
"./permissions": "./src/entrypoints/permissions/index.ts",
|
||||
"./rootConfig": "./src/entrypoints/rootConfig/index.ts",
|
||||
"./rootHttpRouter": "./src/entrypoints/rootHttpRouter/index.ts",
|
||||
"./rootLifecycle": "./src/entrypoints/rootLifecycle/index.ts",
|
||||
"./rootLogger": "./src/entrypoints/rootLogger/index.ts",
|
||||
"./scheduler": "./src/entrypoints/scheduler/index.ts",
|
||||
"./urlReader": "./src/entrypoints/urlReader/index.ts",
|
||||
"./userInfo": "./src/entrypoints/userInfo/index.ts",
|
||||
@@ -53,18 +57,30 @@
|
||||
"httpAuth": [
|
||||
"src/entrypoints/httpAuth/index.ts"
|
||||
],
|
||||
"httpRouter": [
|
||||
"src/entrypoints/httpRouter/index.ts"
|
||||
],
|
||||
"lifecycle": [
|
||||
"src/entrypoints/lifecycle/index.ts"
|
||||
],
|
||||
"logger": [
|
||||
"src/entrypoints/logger/index.ts"
|
||||
],
|
||||
"permissions": [
|
||||
"src/entrypoints/permissions/index.ts"
|
||||
],
|
||||
"rootConfig": [
|
||||
"src/entrypoints/rootConfig/index.ts"
|
||||
],
|
||||
"rootHttpRouter": [
|
||||
"src/entrypoints/rootHttpRouter/index.ts"
|
||||
],
|
||||
"rootLifecycle": [
|
||||
"src/entrypoints/rootLifecycle/index.ts"
|
||||
],
|
||||
"rootLogger": [
|
||||
"src/entrypoints/rootLogger/index.ts"
|
||||
],
|
||||
"scheduler": [
|
||||
"src/entrypoints/scheduler/index.ts"
|
||||
],
|
||||
@@ -103,6 +119,7 @@
|
||||
"@backstage/backend-common": "workspace:^",
|
||||
"@backstage/backend-dev-utils": "workspace:^",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/cli-common": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/config-loader": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
@@ -115,32 +132,49 @@
|
||||
"@google-cloud/storage": "^7.0.0",
|
||||
"@keyv/memcache": "^1.3.5",
|
||||
"@keyv/redis": "^2.5.3",
|
||||
"@manypkg/get-packages": "^1.1.3",
|
||||
"@octokit/rest": "^19.0.3",
|
||||
"@opentelemetry/api": "^1.3.0",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@types/express": "^4.17.6",
|
||||
"archiver": "^6.0.0",
|
||||
"base64-stream": "^1.0.0",
|
||||
"better-sqlite3": "^9.0.0",
|
||||
"compression": "^1.7.4",
|
||||
"concat-stream": "^2.0.0",
|
||||
"cookie": "^0.6.0",
|
||||
"cors": "^2.8.5",
|
||||
"cron": "^3.0.0",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"git-url-parse": "^14.0.0",
|
||||
"helmet": "^6.0.0",
|
||||
"isomorphic-git": "^1.23.0",
|
||||
"jose": "^5.0.0",
|
||||
"keyv": "^4.5.2",
|
||||
"knex": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"logform": "^2.3.2",
|
||||
"luxon": "^3.0.0",
|
||||
"minimatch": "^9.0.0",
|
||||
"minimist": "^1.2.5",
|
||||
"morgan": "^1.10.0",
|
||||
"mysql2": "^3.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-forge": "^1.3.1",
|
||||
"p-limit": "^3.1.0",
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"pg": "^8.11.3",
|
||||
"pg-connection-string": "^2.3.0",
|
||||
"raw-body": "^2.4.1",
|
||||
"selfsigned": "^2.0.0",
|
||||
"stoppable": "^1.1.0",
|
||||
"tar": "^6.1.12",
|
||||
"triple-beam": "^1.4.1",
|
||||
"uuid": "^9.0.0",
|
||||
"winston": "^3.2.1",
|
||||
"winston-transport": "^4.5.0",
|
||||
"yauzl": "^3.0.0",
|
||||
"yn": "^4.0.0",
|
||||
"zod": "^3.22.4"
|
||||
@@ -150,8 +184,14 @@
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@types/http-errors": "^2.0.0",
|
||||
"@types/morgan": "^1.9.0",
|
||||
"@types/node-forge": "^1.3.0",
|
||||
"@types/stoppable": "^1.1.0",
|
||||
"aws-sdk-client-mock": "^4.0.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"msw": "^1.0.0",
|
||||
"supertest": "^6.1.3",
|
||||
"wait-for-expect": "^3.0.2"
|
||||
},
|
||||
"configSchema": "config.d.ts"
|
||||
|
||||
@@ -17,11 +17,7 @@
|
||||
import {
|
||||
Backend,
|
||||
createSpecializedBackend,
|
||||
httpRouterServiceFactory,
|
||||
identityServiceFactory,
|
||||
loggerServiceFactory,
|
||||
rootHttpRouterServiceFactory,
|
||||
rootLoggerServiceFactory,
|
||||
tokenManagerServiceFactory,
|
||||
} from '@backstage/backend-app-api';
|
||||
import { authServiceFactory } from '@backstage/backend-defaults/auth';
|
||||
@@ -29,10 +25,14 @@ import { cacheServiceFactory } from '@backstage/backend-defaults/cache';
|
||||
import { databaseServiceFactory } from '@backstage/backend-defaults/database';
|
||||
import { discoveryServiceFactory } from '@backstage/backend-defaults/discovery';
|
||||
import { httpAuthServiceFactory } from '@backstage/backend-defaults/httpAuth';
|
||||
import { httpRouterServiceFactory } from '@backstage/backend-defaults/httpRouter';
|
||||
import { lifecycleServiceFactory } from '@backstage/backend-defaults/lifecycle';
|
||||
import { loggerServiceFactory } from '@backstage/backend-defaults/logger';
|
||||
import { permissionsServiceFactory } from '@backstage/backend-defaults/permissions';
|
||||
import { rootConfigServiceFactory } from '@backstage/backend-defaults/rootConfig';
|
||||
import { rootHttpRouterServiceFactory } from '@backstage/backend-defaults/rootHttpRouter';
|
||||
import { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle';
|
||||
import { rootLoggerServiceFactory } from '@backstage/backend-defaults/rootLogger';
|
||||
import { schedulerServiceFactory } from '@backstage/backend-defaults/scheduler';
|
||||
import { urlReaderServiceFactory } from '@backstage/backend-defaults/urlReader';
|
||||
import { userInfoServiceFactory } from '@backstage/backend-defaults/userInfo';
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
|
||||
import { Config } from '@backstage/config';
|
||||
import { readHttpServerOptions } from '@backstage/backend-app-api';
|
||||
import { DiscoveryService } from '@backstage/backend-plugin-api';
|
||||
import { readHttpServerOptions } from '../rootHttpRouter/http/config';
|
||||
|
||||
type Target = string | { internal: string; external: string };
|
||||
|
||||
|
||||
+1
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AuthService } from '@backstage/backend-plugin-api';
|
||||
import express from 'express';
|
||||
import Router from 'express-promise-router';
|
||||
+1
-1
@@ -20,7 +20,7 @@ import express from 'express';
|
||||
import request from 'supertest';
|
||||
import { createCredentialsBarrier } from './createCredentialsBarrier';
|
||||
import { mockCredentials, mockServices } from '@backstage/backend-test-utils';
|
||||
import { MiddlewareFactory } from '../../../http';
|
||||
import { MiddlewareFactory } from '../rootHttpRouter/http';
|
||||
|
||||
const errorMiddleware = MiddlewareFactory.create({
|
||||
config: mockServices.rootConfig(),
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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 } from '@backstage/backend-plugin-api';
|
||||
import { ServiceUnavailableError } from '@backstage/errors';
|
||||
import { HumanDuration, durationToMilliseconds } from '@backstage/types';
|
||||
import { RequestHandler } from 'express';
|
||||
|
||||
export const DEFAULT_TIMEOUT = { seconds: 5 };
|
||||
|
||||
/**
|
||||
* Options for {@link createLifecycleMiddleware}.
|
||||
* @public
|
||||
*/
|
||||
export interface LifecycleMiddlewareOptions {
|
||||
lifecycle: LifecycleService;
|
||||
/**
|
||||
* The maximum time that paused requests will wait for the service to start, before returning an error.
|
||||
*
|
||||
* Defaults to 5 seconds.
|
||||
*/
|
||||
startupRequestPauseTimeout?: HumanDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export function createLifecycleMiddleware(
|
||||
options: LifecycleMiddlewareOptions,
|
||||
): RequestHandler {
|
||||
const { lifecycle, startupRequestPauseTimeout = DEFAULT_TIMEOUT } = options;
|
||||
|
||||
let state: 'init' | 'up' | 'down' = 'init';
|
||||
const waiting = new Set<{
|
||||
next: (err?: Error) => void;
|
||||
timeout: NodeJS.Timeout;
|
||||
}>();
|
||||
|
||||
lifecycle.addStartupHook(async () => {
|
||||
if (state === 'init') {
|
||||
state = 'up';
|
||||
for (const item of waiting) {
|
||||
clearTimeout(item.timeout);
|
||||
item.next();
|
||||
}
|
||||
waiting.clear();
|
||||
}
|
||||
});
|
||||
|
||||
lifecycle.addShutdownHook(async () => {
|
||||
state = 'down';
|
||||
|
||||
for (const item of waiting) {
|
||||
clearTimeout(item.timeout);
|
||||
item.next(new ServiceUnavailableError('Service is shutting down'));
|
||||
}
|
||||
waiting.clear();
|
||||
});
|
||||
|
||||
const timeoutMs = durationToMilliseconds(startupRequestPauseTimeout);
|
||||
|
||||
return (_req, _res, next) => {
|
||||
if (state === 'up') {
|
||||
next();
|
||||
return;
|
||||
} else if (state === 'down') {
|
||||
next(new ServiceUnavailableError('Service is shutting down'));
|
||||
return;
|
||||
}
|
||||
|
||||
const item = {
|
||||
next,
|
||||
timeout: setTimeout(() => {
|
||||
if (waiting.delete(item)) {
|
||||
next(new ServiceUnavailableError('Service has not started up yet'));
|
||||
}
|
||||
}, timeoutMs),
|
||||
};
|
||||
|
||||
waiting.add(item);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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 { Handler } from 'express';
|
||||
import PromiseRouter from 'express-promise-router';
|
||||
import {
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
HttpRouterServiceAuthPolicy,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { createLifecycleMiddleware } from './createLifecycleMiddleware';
|
||||
import { createCredentialsBarrier } from './createCredentialsBarrier';
|
||||
import { createAuthIntegrationRouter } from './createAuthIntegrationRouter';
|
||||
import { createCookieAuthRefreshMiddleware } from './createCookieAuthRefreshMiddleware';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface HttpRouterFactoryOptions {
|
||||
/**
|
||||
* A callback used to generate the path for each plugin, defaults to `/api/{pluginId}`.
|
||||
*/
|
||||
getPath?(pluginId: string): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export const httpRouterServiceFactory = createServiceFactory(
|
||||
(options?: HttpRouterFactoryOptions) => ({
|
||||
service: coreServices.httpRouter,
|
||||
initialization: 'always',
|
||||
deps: {
|
||||
plugin: coreServices.pluginMetadata,
|
||||
config: coreServices.rootConfig,
|
||||
logger: coreServices.logger,
|
||||
lifecycle: coreServices.lifecycle,
|
||||
rootHttpRouter: coreServices.rootHttpRouter,
|
||||
auth: coreServices.auth,
|
||||
httpAuth: coreServices.httpAuth,
|
||||
},
|
||||
async factory({
|
||||
auth,
|
||||
httpAuth,
|
||||
config,
|
||||
logger,
|
||||
plugin,
|
||||
rootHttpRouter,
|
||||
lifecycle,
|
||||
}) {
|
||||
if (options?.getPath) {
|
||||
logger.warn(
|
||||
`DEPRECATION WARNING: The 'getPath' option for HttpRouterService is deprecated. The ability to reconfigure the '/api/' path prefix for plugins will be removed in the future.`,
|
||||
);
|
||||
}
|
||||
const getPath = options?.getPath ?? (id => `/api/${id}`);
|
||||
const path = getPath(plugin.getId());
|
||||
|
||||
const router = PromiseRouter();
|
||||
rootHttpRouter.use(path, router);
|
||||
|
||||
const credentialsBarrier = createCredentialsBarrier({
|
||||
httpAuth,
|
||||
config,
|
||||
});
|
||||
|
||||
router.use(createAuthIntegrationRouter({ auth }));
|
||||
router.use(createLifecycleMiddleware({ lifecycle }));
|
||||
router.use(credentialsBarrier.middleware);
|
||||
router.use(createCookieAuthRefreshMiddleware({ auth, httpAuth }));
|
||||
|
||||
return {
|
||||
use(handler: Handler): void {
|
||||
router.use(handler);
|
||||
},
|
||||
addAuthPolicy(policy: HttpRouterServiceAuthPolicy): void {
|
||||
credentialsBarrier.addAuthPolicy(policy);
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 type { HttpRouterFactoryOptions } from './httpRouterServiceFactory';
|
||||
export { createLifecycleMiddleware } from './createLifecycleMiddleware';
|
||||
export type { LifecycleMiddlewareOptions } from './createLifecycleMiddleware';
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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';
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export const loggerServiceFactory = createServiceFactory({
|
||||
service: coreServices.logger,
|
||||
deps: {
|
||||
rootLogger: coreServices.rootLogger,
|
||||
plugin: coreServices.pluginMetadata,
|
||||
},
|
||||
factory({ rootLogger, plugin }) {
|
||||
return rootLogger.child({ plugin: plugin.getId() });
|
||||
},
|
||||
});
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 { loadConfigSchema } from '@backstage/config-loader';
|
||||
import { createConfigSecretEnumerator } from './createConfigSecretEnumerator';
|
||||
import { mockServices } from '@backstage/backend-test-utils';
|
||||
|
||||
describe('createConfigSecretEnumerator', () => {
|
||||
it('should enumerate secrets', async () => {
|
||||
const logger = mockServices.logger.mock();
|
||||
|
||||
const enumerate = await createConfigSecretEnumerator({
|
||||
logger,
|
||||
});
|
||||
const secrets = enumerate(
|
||||
mockServices.rootConfig({
|
||||
data: {
|
||||
backend: { auth: { keys: [{ secret: 'my-secret-password' }] } },
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(Array.from(secrets)).toEqual(['my-secret-password']);
|
||||
}, 20_000); // Bit higher timeout since we're loading all config schemas in the repo
|
||||
|
||||
it('should enumerate secrets with explicit schema', async () => {
|
||||
const logger = mockServices.logger.mock();
|
||||
|
||||
const enumerate = await createConfigSecretEnumerator({
|
||||
logger,
|
||||
schema: await loadConfigSchema({
|
||||
serialized: {
|
||||
schemas: [
|
||||
{
|
||||
value: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
secret: {
|
||||
visibility: 'secret',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
path: '/mock',
|
||||
},
|
||||
],
|
||||
backstageConfigSchemaVersion: 1,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const secrets = enumerate(
|
||||
mockServices.rootConfig({
|
||||
data: {
|
||||
secret: 'my-secret',
|
||||
other: 'not-secret',
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(Array.from(secrets)).toEqual(['my-secret']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import type { Config } from '@backstage/config';
|
||||
import { ConfigSchema, loadConfigSchema } from '@backstage/config-loader';
|
||||
import { getPackages } from '@manypkg/get-packages';
|
||||
|
||||
/** @public */
|
||||
export async function createConfigSecretEnumerator(options: {
|
||||
logger: LoggerService;
|
||||
dir?: string;
|
||||
schema?: ConfigSchema;
|
||||
}): Promise<(config: Config) => Iterable<string>> {
|
||||
const { logger, dir = process.cwd() } = options;
|
||||
const { packages } = await getPackages(dir);
|
||||
const schema =
|
||||
options.schema ??
|
||||
(await loadConfigSchema({
|
||||
dependencies: packages.map(p => p.packageJson.name),
|
||||
}));
|
||||
|
||||
return (config: Config) => {
|
||||
const [secretsData] = schema.process(
|
||||
[{ data: config.getOptional() ?? {}, context: 'schema-enumerator' }],
|
||||
{
|
||||
visibility: ['secret'],
|
||||
ignoreSchemaErrors: true,
|
||||
},
|
||||
);
|
||||
const secrets = new Set<string>();
|
||||
JSON.parse(
|
||||
JSON.stringify(secretsData.data),
|
||||
(_, v) => typeof v === 'string' && secrets.add(v),
|
||||
);
|
||||
logger.info(
|
||||
`Found ${secrets.size} new secrets in config that will be redacted`,
|
||||
);
|
||||
return secrets;
|
||||
};
|
||||
}
|
||||
@@ -14,5 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { rootConfigServiceFactory } from './rootConfigServiceFactory';
|
||||
export type { RootConfigFactoryOptions } from './rootConfigServiceFactory';
|
||||
export { createConfigSecretEnumerator } from './createConfigSecretEnumerator';
|
||||
export {
|
||||
rootConfigServiceFactory,
|
||||
type RootConfigFactoryOptions,
|
||||
} from './rootConfigServiceFactory';
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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';
|
||||
import { Handler, Router } from 'express';
|
||||
import trimEnd from 'lodash/trimEnd';
|
||||
|
||||
function normalizePath(path: string): string {
|
||||
return `${trimEnd(path, '/')}/`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for the {@link DefaultRootHttpRouter} class.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface DefaultRootHttpRouterOptions {
|
||||
/**
|
||||
* The path to forward all unmatched requests to. Defaults to '/api/app' if
|
||||
* not given. Disables index path behavior if false is given.
|
||||
*/
|
||||
indexPath?: string | false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default implementation of the {@link @backstage/backend-plugin-api#RootHttpRouterService} interface for
|
||||
* {@link @backstage/backend-plugin-api#coreServices.rootHttpRouter}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class DefaultRootHttpRouter implements RootHttpRouterService {
|
||||
#indexPath?: string;
|
||||
|
||||
#router = Router();
|
||||
#namedRoutes = Router();
|
||||
#indexRouter = Router();
|
||||
#existingPaths = new Array<string>();
|
||||
|
||||
static create(options?: DefaultRootHttpRouterOptions) {
|
||||
let indexPath;
|
||||
if (options?.indexPath === false) {
|
||||
indexPath = undefined;
|
||||
} else if (options?.indexPath === undefined) {
|
||||
indexPath = '/api/app';
|
||||
} else if (options?.indexPath === '') {
|
||||
throw new Error('indexPath option may not be an empty string');
|
||||
} else {
|
||||
indexPath = options.indexPath;
|
||||
}
|
||||
return new DefaultRootHttpRouter(indexPath);
|
||||
}
|
||||
|
||||
private constructor(indexPath?: string) {
|
||||
this.#indexPath = indexPath;
|
||||
this.#router.use(this.#namedRoutes);
|
||||
|
||||
// Any request with a /api/ prefix will skip the index router, even if no named router matches
|
||||
this.#router.use('/api/', (_req, _res, next) => {
|
||||
next('router');
|
||||
});
|
||||
|
||||
if (this.#indexPath) {
|
||||
this.#router.use(this.#indexRouter);
|
||||
}
|
||||
}
|
||||
|
||||
use(path: string, handler: Handler) {
|
||||
if (path.match(/^[/\s]*$/)) {
|
||||
throw new Error(`Root router path may not be empty`);
|
||||
}
|
||||
const conflictingPath = this.#findConflictingPath(path);
|
||||
if (conflictingPath) {
|
||||
throw new Error(
|
||||
`Path ${path} conflicts with the existing path ${conflictingPath}`,
|
||||
);
|
||||
}
|
||||
this.#existingPaths.push(path);
|
||||
this.#namedRoutes.use(path, handler);
|
||||
|
||||
if (this.#indexPath === path) {
|
||||
this.#indexRouter.use(handler);
|
||||
}
|
||||
}
|
||||
|
||||
handler(): Handler {
|
||||
return this.#router;
|
||||
}
|
||||
|
||||
#findConflictingPath(newPath: string): string | undefined {
|
||||
const normalizedNewPath = normalizePath(newPath);
|
||||
for (const path of this.#existingPaths) {
|
||||
const normalizedPath = normalizePath(path);
|
||||
if (normalizedPath.startsWith(normalizedNewPath)) {
|
||||
return path;
|
||||
}
|
||||
if (normalizedNewPath.startsWith(normalizedPath)) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 { readHttpServerOptions } from './config';
|
||||
export { createHttpServer } from './createHttpServer';
|
||||
export { MiddlewareFactory } from './MiddlewareFactory';
|
||||
export type {
|
||||
MiddlewareFactoryErrorOptions,
|
||||
MiddlewareFactoryOptions,
|
||||
} from './MiddlewareFactory';
|
||||
export { readCorsOptions } from './readCorsOptions';
|
||||
export { readHelmetOptions } from './readHelmetOptions';
|
||||
export type {
|
||||
ExtendedHttpServer,
|
||||
HttpServerCertificateOptions,
|
||||
HttpServerOptions,
|
||||
} from './types';
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 {
|
||||
DefaultRootHttpRouter,
|
||||
type DefaultRootHttpRouterOptions,
|
||||
} from './DefaultRootHttpRouter';
|
||||
export * from './http';
|
||||
export {
|
||||
rootHttpRouterServiceFactory,
|
||||
type RootHttpRouterConfigureContext,
|
||||
type RootHttpRouterFactoryOptions,
|
||||
} from './rootHttpRouterServiceFactory';
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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 {
|
||||
RootConfigService,
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
LifecycleService,
|
||||
LoggerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import express, { RequestHandler, Express } from 'express';
|
||||
import type { Server } from 'node:http';
|
||||
import {
|
||||
createHttpServer,
|
||||
MiddlewareFactory,
|
||||
readHttpServerOptions,
|
||||
} from './http';
|
||||
import { DefaultRootHttpRouter } from './DefaultRootHttpRouter';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface RootHttpRouterConfigureContext {
|
||||
app: Express;
|
||||
server: Server;
|
||||
middleware: MiddlewareFactory;
|
||||
routes: RequestHandler;
|
||||
config: RootConfigService;
|
||||
logger: LoggerService;
|
||||
lifecycle: LifecycleService;
|
||||
applyDefaults: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export type RootHttpRouterFactoryOptions = {
|
||||
/**
|
||||
* The path to forward all unmatched requests to. Defaults to '/api/app' if
|
||||
* not given. Disables index path behavior if false is given.
|
||||
*/
|
||||
indexPath?: string | false;
|
||||
|
||||
configure?(context: RootHttpRouterConfigureContext): void;
|
||||
};
|
||||
|
||||
function defaultConfigure({ applyDefaults }: RootHttpRouterConfigureContext) {
|
||||
applyDefaults();
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export const rootHttpRouterServiceFactory = createServiceFactory(
|
||||
(options?: RootHttpRouterFactoryOptions) => ({
|
||||
service: coreServices.rootHttpRouter,
|
||||
deps: {
|
||||
config: coreServices.rootConfig,
|
||||
rootLogger: coreServices.rootLogger,
|
||||
lifecycle: coreServices.rootLifecycle,
|
||||
},
|
||||
async factory({ config, rootLogger, lifecycle }) {
|
||||
const { indexPath, configure = defaultConfigure } = options ?? {};
|
||||
const logger = rootLogger.child({ service: 'rootHttpRouter' });
|
||||
const app = express();
|
||||
|
||||
const router = DefaultRootHttpRouter.create({ indexPath });
|
||||
const middleware = MiddlewareFactory.create({ config, logger });
|
||||
const routes = router.handler();
|
||||
const server = await createHttpServer(
|
||||
app,
|
||||
readHttpServerOptions(config.getOptionalConfig('backend')),
|
||||
{ logger },
|
||||
);
|
||||
|
||||
configure({
|
||||
app,
|
||||
server,
|
||||
routes,
|
||||
middleware,
|
||||
config,
|
||||
logger,
|
||||
lifecycle,
|
||||
applyDefaults() {
|
||||
app.use(middleware.helmet());
|
||||
app.use(middleware.cors());
|
||||
app.use(middleware.compression());
|
||||
app.use(middleware.logging());
|
||||
app.use(routes);
|
||||
app.use(middleware.notFound());
|
||||
app.use(middleware.error());
|
||||
},
|
||||
});
|
||||
|
||||
lifecycle.addShutdownHook(() => server.stop());
|
||||
|
||||
await server.start();
|
||||
|
||||
return router;
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* 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 {
|
||||
LoggerService,
|
||||
RootLoggerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { Format, TransformableInfo } from 'logform';
|
||||
import {
|
||||
Logger,
|
||||
format,
|
||||
createLogger,
|
||||
transports,
|
||||
transport as Transport,
|
||||
} from 'winston';
|
||||
import { MESSAGE } from 'triple-beam';
|
||||
import { escapeRegExp } from '../../lib/escapeRegExp';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface WinstonLoggerOptions {
|
||||
meta?: JsonObject;
|
||||
level?: string;
|
||||
format?: Format;
|
||||
transports?: Transport[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link @backstage/backend-plugin-api#LoggerService} implementation based on winston.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class WinstonLogger implements RootLoggerService {
|
||||
#winston: Logger;
|
||||
#addRedactions?: (redactions: Iterable<string>) => void;
|
||||
|
||||
/**
|
||||
* Creates a {@link WinstonLogger} instance.
|
||||
*/
|
||||
static create(options: WinstonLoggerOptions): WinstonLogger {
|
||||
const redacter = WinstonLogger.redacter();
|
||||
const defaultFormatter =
|
||||
process.env.NODE_ENV === 'production'
|
||||
? format.json()
|
||||
: WinstonLogger.colorFormat();
|
||||
|
||||
let logger = createLogger({
|
||||
level: process.env.LOG_LEVEL || options.level || 'info',
|
||||
format: format.combine(
|
||||
options.format ?? defaultFormatter,
|
||||
redacter.format,
|
||||
),
|
||||
transports: options.transports ?? new transports.Console(),
|
||||
});
|
||||
|
||||
if (options.meta) {
|
||||
logger = logger.child(options.meta);
|
||||
}
|
||||
|
||||
return new WinstonLogger(logger, redacter.add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a winston log formatter for redacting secrets.
|
||||
*/
|
||||
static redacter(): {
|
||||
format: Format;
|
||||
add: (redactions: Iterable<string>) => void;
|
||||
} {
|
||||
const redactionSet = new Set<string>();
|
||||
|
||||
let redactionPattern: RegExp | undefined = undefined;
|
||||
|
||||
return {
|
||||
format: format((obj: TransformableInfo) => {
|
||||
if (!redactionPattern || !obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
obj[MESSAGE] = obj[MESSAGE]?.replace?.(redactionPattern, '***');
|
||||
|
||||
return obj;
|
||||
})(),
|
||||
add(newRedactions) {
|
||||
let added = 0;
|
||||
for (const redactionToTrim of newRedactions) {
|
||||
// Trimming the string ensures that we don't accdentally get extra
|
||||
// newlines or other whitespace interfering with the redaction; this
|
||||
// can happen for example when using string literals in yaml
|
||||
const redaction = redactionToTrim.trim();
|
||||
// Exclude secrets that are empty or just one character in length. These
|
||||
// typically mean that you are running local dev or tests, or using the
|
||||
// --lax flag which sets things to just 'x'.
|
||||
if (redaction.length <= 1) {
|
||||
continue;
|
||||
}
|
||||
if (!redactionSet.has(redaction)) {
|
||||
redactionSet.add(redaction);
|
||||
added += 1;
|
||||
}
|
||||
}
|
||||
if (added > 0) {
|
||||
const redactions = Array.from(redactionSet)
|
||||
.map(r => escapeRegExp(r))
|
||||
.join('|');
|
||||
redactionPattern = new RegExp(`(${redactions})`, 'g');
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a pretty printed winston log formatter.
|
||||
*/
|
||||
static colorFormat(): Format {
|
||||
const colorizer = format.colorize();
|
||||
|
||||
return format.combine(
|
||||
format.timestamp(),
|
||||
format.colorize({
|
||||
colors: {
|
||||
timestamp: 'dim',
|
||||
prefix: 'blue',
|
||||
field: 'cyan',
|
||||
debug: 'grey',
|
||||
},
|
||||
}),
|
||||
format.printf((info: TransformableInfo) => {
|
||||
const { timestamp, level, message, plugin, service, ...fields } = info;
|
||||
const prefix = plugin || service;
|
||||
const timestampColor = colorizer.colorize('timestamp', timestamp);
|
||||
const prefixColor = colorizer.colorize('prefix', prefix);
|
||||
|
||||
const extraFields = Object.entries(fields)
|
||||
.map(
|
||||
([key, value]) =>
|
||||
`${colorizer.colorize('field', `${key}`)}=${value}`,
|
||||
)
|
||||
.join(' ');
|
||||
|
||||
return `${timestampColor} ${prefixColor} ${level} ${message} ${extraFields}`;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
winston: Logger,
|
||||
addRedactions?: (redactions: Iterable<string>) => void,
|
||||
) {
|
||||
this.#winston = winston;
|
||||
this.#addRedactions = addRedactions;
|
||||
}
|
||||
|
||||
error(message: string, meta?: JsonObject): void {
|
||||
this.#winston.error(message, meta);
|
||||
}
|
||||
|
||||
warn(message: string, meta?: JsonObject): void {
|
||||
this.#winston.warn(message, meta);
|
||||
}
|
||||
|
||||
info(message: string, meta?: JsonObject): void {
|
||||
this.#winston.info(message, meta);
|
||||
}
|
||||
|
||||
debug(message: string, meta?: JsonObject): void {
|
||||
this.#winston.debug(message, meta);
|
||||
}
|
||||
|
||||
child(meta: JsonObject): LoggerService {
|
||||
return new WinstonLogger(this.#winston.child(meta));
|
||||
}
|
||||
|
||||
addRedactions(redactions: Iterable<string>) {
|
||||
this.#addRedactions?.(redactions);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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';
|
||||
export { WinstonLogger, type WinstonLoggerOptions } from './WinstonLogger';
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { transports, format } from 'winston';
|
||||
import { WinstonLogger } from '../rootLogger/WinstonLogger';
|
||||
import { createConfigSecretEnumerator } from '../rootConfig/createConfigSecretEnumerator';
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export const rootLoggerServiceFactory = createServiceFactory({
|
||||
service: coreServices.rootLogger,
|
||||
deps: {
|
||||
config: coreServices.rootConfig,
|
||||
},
|
||||
async factory({ config }) {
|
||||
const logger = WinstonLogger.create({
|
||||
meta: {
|
||||
service: 'backstage',
|
||||
},
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format:
|
||||
process.env.NODE_ENV === 'production'
|
||||
? format.json()
|
||||
: WinstonLogger.colorFormat(),
|
||||
transports: [new transports.Console()],
|
||||
});
|
||||
|
||||
const secretEnumerator = await createConfigSecretEnumerator({ logger });
|
||||
logger.addRedactions(secretEnumerator(config));
|
||||
config.subscribe?.(() => logger.addRedactions(secretEnumerator(config)));
|
||||
|
||||
return logger;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2021 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 { isValidUrl } from './urls';
|
||||
|
||||
describe('isValidUrl', () => {
|
||||
it('should return true for url', () => {
|
||||
const validUrl = isValidUrl('http://some.valid.url');
|
||||
expect(validUrl).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for absolute path', () => {
|
||||
const validUrl = isValidUrl('/some/absolute/path');
|
||||
expect(validUrl).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for relative path', () => {
|
||||
const validUrl = isValidUrl('../some/relative/path');
|
||||
expect(validUrl).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2021 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 function isValidUrl(url: string): boolean {
|
||||
try {
|
||||
// eslint-disable-next-line no-new
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import { EventsService } from '@backstage/plugin-events-node';
|
||||
import { ExtendedHttpServer } from '@backstage/backend-app-api';
|
||||
import { ExtensionPoint } from '@backstage/backend-plugin-api';
|
||||
import { HttpAuthService } from '@backstage/backend-plugin-api';
|
||||
import { HttpRouterFactoryOptions } from '@backstage/backend-app-api';
|
||||
import { HttpRouterFactoryOptions } from '@backstage/backend-defaults/httpRouter';
|
||||
import { HttpRouterService } from '@backstage/backend-plugin-api';
|
||||
import { IdentityService } from '@backstage/backend-plugin-api';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
@@ -32,7 +32,7 @@ import { LifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { PermissionsService } from '@backstage/backend-plugin-api';
|
||||
import { RootConfigService } from '@backstage/backend-plugin-api';
|
||||
import { RootHttpRouterFactoryOptions } from '@backstage/backend-app-api';
|
||||
import { RootHttpRouterFactoryOptions } from '@backstage/backend-defaults/rootHttpRouter';
|
||||
import { RootHttpRouterService } from '@backstage/backend-plugin-api';
|
||||
import { RootLifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { RootLoggerService } from '@backstage/backend-plugin-api';
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-app-api": "workspace:^",
|
||||
"@backstage/backend-defaults": "workspace:^",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
|
||||
@@ -14,48 +14,48 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { cacheServiceFactory } from '@backstage/backend-defaults/cache';
|
||||
import { databaseServiceFactory } from '@backstage/backend-defaults/database';
|
||||
import {
|
||||
RootConfigService,
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
HostDiscovery,
|
||||
discoveryServiceFactory,
|
||||
} from '@backstage/backend-defaults/discovery';
|
||||
import { httpRouterServiceFactory } from '@backstage/backend-defaults/httpRouter';
|
||||
import { lifecycleServiceFactory } from '@backstage/backend-defaults/lifecycle';
|
||||
import { loggerServiceFactory } from '@backstage/backend-defaults/logger';
|
||||
import { permissionsServiceFactory } from '@backstage/backend-defaults/permissions';
|
||||
import { rootHttpRouterServiceFactory } from '@backstage/backend-defaults/rootHttpRouter';
|
||||
import { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle';
|
||||
import { schedulerServiceFactory } from '@backstage/backend-defaults/scheduler';
|
||||
import { urlReaderServiceFactory } from '@backstage/backend-defaults/urlReader';
|
||||
import {
|
||||
AuthService,
|
||||
BackstageCredentials,
|
||||
BackstageUserInfo,
|
||||
DiscoveryService,
|
||||
HttpAuthService,
|
||||
IdentityService,
|
||||
LoggerService,
|
||||
RootConfigService,
|
||||
ServiceFactory,
|
||||
ServiceRef,
|
||||
TokenManagerService,
|
||||
AuthService,
|
||||
DiscoveryService,
|
||||
HttpAuthService,
|
||||
BackstageCredentials,
|
||||
BackstageUserInfo,
|
||||
UserInfoService,
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
cacheServiceFactory,
|
||||
databaseServiceFactory,
|
||||
httpRouterServiceFactory,
|
||||
lifecycleServiceFactory,
|
||||
loggerServiceFactory,
|
||||
permissionsServiceFactory,
|
||||
rootHttpRouterServiceFactory,
|
||||
rootLifecycleServiceFactory,
|
||||
schedulerServiceFactory,
|
||||
urlReaderServiceFactory,
|
||||
discoveryServiceFactory,
|
||||
HostDiscovery,
|
||||
} from '@backstage/backend-app-api';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { MockIdentityService } from './MockIdentityService';
|
||||
import { MockRootLoggerService } from './MockRootLoggerService';
|
||||
import { MockAuthService } from './MockAuthService';
|
||||
import { MockHttpAuthService } from './MockHttpAuthService';
|
||||
import { mockCredentials } from './mockCredentials';
|
||||
import { MockUserInfoService } from './MockUserInfoService';
|
||||
import {
|
||||
eventsServiceFactory,
|
||||
eventsServiceRef,
|
||||
} from '@backstage/plugin-events-node';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { MockAuthService } from './MockAuthService';
|
||||
import { MockHttpAuthService } from './MockHttpAuthService';
|
||||
import { MockIdentityService } from './MockIdentityService';
|
||||
import { MockRootLoggerService } from './MockRootLoggerService';
|
||||
import { MockUserInfoService } from './MockUserInfoService';
|
||||
import { mockCredentials } from './mockCredentials';
|
||||
|
||||
/** @internal */
|
||||
function createLoggerMock() {
|
||||
|
||||
@@ -3457,6 +3457,7 @@ __metadata:
|
||||
resolution: "@backstage/backend-app-api@workspace:packages/backend-app-api"
|
||||
dependencies:
|
||||
"@backstage/backend-common": "workspace:^"
|
||||
"@backstage/backend-defaults": "workspace:^"
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/backend-tasks": "workspace:^"
|
||||
"@backstage/backend-test-utils": "workspace:^"
|
||||
@@ -3611,6 +3612,7 @@ __metadata:
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/backend-test-utils": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/cli-common": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/config-loader": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
@@ -3623,35 +3625,58 @@ __metadata:
|
||||
"@google-cloud/storage": ^7.0.0
|
||||
"@keyv/memcache": ^1.3.5
|
||||
"@keyv/redis": ^2.5.3
|
||||
"@manypkg/get-packages": ^1.1.3
|
||||
"@octokit/rest": ^19.0.3
|
||||
"@opentelemetry/api": ^1.3.0
|
||||
"@types/cors": ^2.8.6
|
||||
"@types/express": ^4.17.6
|
||||
"@types/http-errors": ^2.0.0
|
||||
"@types/morgan": ^1.9.0
|
||||
"@types/node-forge": ^1.3.0
|
||||
"@types/stoppable": ^1.1.0
|
||||
archiver: ^6.0.0
|
||||
aws-sdk-client-mock: ^4.0.0
|
||||
base64-stream: ^1.0.0
|
||||
better-sqlite3: ^9.0.0
|
||||
compression: ^1.7.4
|
||||
concat-stream: ^2.0.0
|
||||
cookie: ^0.6.0
|
||||
cors: ^2.8.5
|
||||
cron: ^3.0.0
|
||||
express: ^4.17.1
|
||||
express-promise-router: ^4.1.0
|
||||
fs-extra: ^11.2.0
|
||||
git-url-parse: ^14.0.0
|
||||
helmet: ^6.0.0
|
||||
http-errors: ^2.0.0
|
||||
isomorphic-git: ^1.23.0
|
||||
jose: ^5.0.0
|
||||
keyv: ^4.5.2
|
||||
knex: ^3.0.0
|
||||
lodash: ^4.17.21
|
||||
logform: ^2.3.2
|
||||
luxon: ^3.0.0
|
||||
minimatch: ^9.0.0
|
||||
minimist: ^1.2.5
|
||||
morgan: ^1.10.0
|
||||
msw: ^1.0.0
|
||||
mysql2: ^3.0.0
|
||||
node-fetch: ^2.6.7
|
||||
node-forge: ^1.3.1
|
||||
p-limit: ^3.1.0
|
||||
path-to-regexp: ^6.2.1
|
||||
pg: ^8.11.3
|
||||
pg-connection-string: ^2.3.0
|
||||
raw-body: ^2.4.1
|
||||
selfsigned: ^2.0.0
|
||||
stoppable: ^1.1.0
|
||||
supertest: ^6.1.3
|
||||
tar: ^6.1.12
|
||||
triple-beam: ^1.4.1
|
||||
uuid: ^9.0.0
|
||||
wait-for-expect: ^3.0.2
|
||||
winston: ^3.2.1
|
||||
winston-transport: ^4.5.0
|
||||
yauzl: ^3.0.0
|
||||
yn: ^4.0.0
|
||||
zod: ^3.22.4
|
||||
@@ -3771,6 +3796,7 @@ __metadata:
|
||||
resolution: "@backstage/backend-test-utils@workspace:packages/backend-test-utils"
|
||||
dependencies:
|
||||
"@backstage/backend-app-api": "workspace:^"
|
||||
"@backstage/backend-defaults": "workspace:^"
|
||||
"@backstage/backend-plugin-api": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
|
||||
Reference in New Issue
Block a user