Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { FeatureDiscoveryService } from '@backstage/backend-plugin-api/alpha';
+import { ServiceFactory } from '@backstage/backend-plugin-api';
+
+// @alpha (undocumented)
+export const featureDiscoveryServiceFactory: () => ServiceFactory<
+ FeatureDiscoveryService,
+ 'root'
+>;
+
+// (No @packageDocumentation comment for this package)
+```
diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md
index 5f452f9e03..0fa1ff376e 100644
--- a/packages/backend-app-api/api-report.md
+++ b/packages/backend-app-api/api-report.md
@@ -9,7 +9,6 @@ import type { AppConfig } from '@backstage/config';
import { BackendFeature } from '@backstage/backend-plugin-api';
import { CacheClient } from '@backstage/backend-common';
import { Config } from '@backstage/config';
-import { ConfigService } from '@backstage/backend-plugin-api';
import { CorsOptions } from 'cors';
import { ErrorRequestHandler } from 'express';
import { Express as Express_2 } from 'express';
@@ -30,6 +29,7 @@ import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { RemoteConfigSourceOptions } from '@backstage/config-loader';
import { RequestHandler } from 'express';
import { RequestListener } from 'http';
+import { RootConfigService } from '@backstage/backend-plugin-api';
import { RootHttpRouterService } from '@backstage/backend-plugin-api';
import { RootLifecycleService } from '@backstage/backend-plugin-api';
import { RootLoggerService } from '@backstage/backend-plugin-api';
@@ -43,7 +43,7 @@ import { UrlReader } from '@backstage/backend-common';
// @public (undocumented)
export interface Backend {
// (undocumented)
- add(feature: BackendFeature): void;
+ add(feature: BackendFeature | (() => BackendFeature)): void;
// (undocumented)
start(): Promise
;
// (undocumented)
@@ -53,17 +53,6 @@ export interface Backend {
// @public (undocumented)
export const cacheServiceFactory: () => ServiceFactory;
-// @public (undocumented)
-export interface ConfigFactoryOptions {
- argv?: string[];
- remote?: Pick;
-}
-
-// @public (undocumented)
-export const configServiceFactory: (
- options?: ConfigFactoryOptions | undefined,
-) => ServiceFactory;
-
// @public (undocumented)
export function createConfigSecretEnumerator(options: {
logger: LoggerService;
@@ -92,7 +81,7 @@ export function createSpecializedBackend(
// @public (undocumented)
export interface CreateSpecializedBackendOptions {
// (undocumented)
- services: ServiceFactoryOrFunction[];
+ defaultServiceFactories: ServiceFactoryOrFunction[];
}
// @public (undocumented)
@@ -224,7 +213,7 @@ export interface MiddlewareFactoryErrorOptions {
// @public
export interface MiddlewareFactoryOptions {
// (undocumented)
- config: ConfigService;
+ config: RootConfigService;
// (undocumented)
logger: LoggerService;
}
@@ -244,12 +233,23 @@ export function readHelmetOptions(config?: Config): HelmetOptions;
// @public
export function readHttpServerOptions(config?: Config): HttpServerOptions;
+// @public (undocumented)
+export interface RootConfigFactoryOptions {
+ argv?: string[];
+ remote?: Pick;
+}
+
+// @public (undocumented)
+export const rootConfigServiceFactory: (
+ options?: RootConfigFactoryOptions | undefined,
+) => ServiceFactory;
+
// @public (undocumented)
export interface RootHttpRouterConfigureContext {
// (undocumented)
app: Express_2;
// (undocumented)
- config: ConfigService;
+ config: RootConfigService;
// (undocumented)
lifecycle: LifecycleService;
// (undocumented)
diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json
index 6f8a5cf9e8..82e32311aa 100644
--- a/packages/backend-app-api/package.json
+++ b/packages/backend-app-api/package.json
@@ -1,17 +1,30 @@
{
"name": "@backstage/backend-app-api",
"description": "Core API used by Backstage backend apps",
- "version": "0.4.6-next.0",
+ "version": "0.5.0",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
- "access": "public",
- "main": "dist/index.cjs.js",
- "types": "dist/index.d.ts"
+ "access": "public"
},
"backstage": {
"role": "node-library"
},
+ "exports": {
+ ".": "./src/index.ts",
+ "./alpha": "./src/alpha.ts",
+ "./package.json": "./package.json"
+ },
+ "typesVersions": {
+ "*": {
+ "alpha": [
+ "src/alpha.ts"
+ ],
+ "package.json": [
+ "package.json"
+ ]
+ }
+ },
"homepage": "https://backstage.io",
"repository": {
"type": "git",
@@ -36,6 +49,7 @@
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/backend-tasks": "workspace:^",
"@backstage/cli-common": "workspace:^",
+ "@backstage/cli-node": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/config-loader": "workspace:^",
"@backstage/errors": "workspace:^",
@@ -73,6 +87,7 @@
"@types/node-forge": "^1.3.0",
"@types/stoppable": "^1.1.0",
"http-errors": "^2.0.0",
+ "mock-fs": "^5.2.0",
"supertest": "^6.1.3"
},
"files": [
diff --git a/packages/backend-app-api/src/alpha.ts b/packages/backend-app-api/src/alpha.ts
new file mode 100644
index 0000000000..a334214738
--- /dev/null
+++ b/packages/backend-app-api/src/alpha.ts
@@ -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 { featureDiscoveryServiceFactory } from './alpha/featureDiscoveryServiceFactory';
diff --git a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts
new file mode 100644
index 0000000000..5d4b1fc551
--- /dev/null
+++ b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts
@@ -0,0 +1,114 @@
+/*
+ * 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 mockFs from 'mock-fs';
+import { resolve as resolvePath, dirname } from 'path';
+import { startTestBackend, mockServices } from '@backstage/backend-test-utils';
+import { featureDiscoveryServiceFactory } from './featureDiscoveryServiceFactory';
+import {
+ coreServices,
+ createServiceFactory,
+} from '@backstage/backend-plugin-api';
+
+const rootDir = dirname(process.argv[1]);
+
+describe('featureDiscoveryServiceFactory', () => {
+ beforeEach(() => {
+ mockFs({
+ [rootDir]: {
+ 'package.json': JSON.stringify({
+ name: 'example-app',
+ dependencies: {
+ 'detected-plugin': '0.0.0',
+ 'detected-module': '0.0.0',
+ },
+ }),
+ },
+ [resolvePath(rootDir, 'node_modules/detected-plugin')]: {
+ 'package.json': JSON.stringify({
+ name: 'detected-plugin',
+ main: 'index.js',
+ backstage: {
+ role: 'backend-plugin',
+ },
+ }),
+ 'index.js': `
+ const { createBackendPlugin, coreServices } = require('@backstage/backend-plugin-api');
+ exports.detectedPlugin = createBackendPlugin({
+ pluginId: 'detected',
+ register(env) {
+ env.registerInit({
+ deps: { identity: coreServices.identity },
+ async init({ identity }) {
+ identity.getIdentity('detected-plugin');
+ },
+ });
+ },
+ });
+ `,
+ },
+ [resolvePath(rootDir, 'node_modules/detected-module')]: {
+ 'package.json': JSON.stringify({
+ name: 'detected-module',
+ main: 'index.js',
+ backstage: {
+ role: 'backend-module',
+ },
+ }),
+ 'index.js': `
+ const { createBackendModule, coreServices } = require('@backstage/backend-plugin-api');
+ exports.detectedModuleDerp = createBackendModule({
+ pluginId: 'detected',
+ moduleId: 'derp',
+ register(env) {
+ env.registerInit({
+ deps: { identity: coreServices.identity },
+ async init({ identity }) {
+ identity.getIdentity('detected-module');
+ },
+ });
+ },
+ });
+ `,
+ },
+ });
+ });
+
+ afterEach(() => {
+ mockFs.restore();
+ });
+
+ it('should detect plugin and module packages', async () => {
+ const fn = jest.fn().mockResolvedValue({});
+
+ await startTestBackend({
+ features: [
+ createServiceFactory({
+ service: coreServices.identity,
+ deps: {},
+ factory: () => ({ getIdentity: fn }),
+ }),
+ featureDiscoveryServiceFactory(),
+ mockServices.rootConfig.factory({
+ data: { backend: { packages: 'all' } },
+ }),
+ ],
+ });
+
+ expect(fn).toHaveBeenCalledWith('detected-plugin');
+ expect(fn).toHaveBeenCalledWith('detected-module');
+ });
+});
diff --git a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts
new file mode 100644
index 0000000000..f9ee7edc5f
--- /dev/null
+++ b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts
@@ -0,0 +1,129 @@
+/*
+ * 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 {
+ BackendFeature,
+ RootConfigService,
+ coreServices,
+ createServiceFactory,
+} from '@backstage/backend-plugin-api';
+import {
+ featureDiscoveryServiceRef,
+ FeatureDiscoveryService,
+} from '@backstage/backend-plugin-api/alpha';
+import { resolve as resolvePath, dirname } from 'path';
+import fs from 'fs-extra';
+import { BackstagePackageJson } from '@backstage/cli-node';
+
+const LOADED_PACKAGE_ROLES = ['backend-plugin', 'backend-module'];
+
+/** @internal */
+async function findClosestPackageDir(
+ searchDir: string,
+): Promise {
+ let path = searchDir;
+
+ // Some confidence check to avoid infinite loop
+ for (let i = 0; i < 1000; i++) {
+ const packagePath = resolvePath(path, 'package.json');
+ const exists = await fs.pathExists(packagePath);
+ if (exists) {
+ return path;
+ }
+
+ const newPath = dirname(path);
+ if (newPath === path) {
+ return undefined;
+ }
+ path = newPath;
+ }
+
+ throw new Error(
+ `Iteration limit reached when searching for root package.json at ${searchDir}`,
+ );
+}
+
+/** @internal */
+class PackageDiscoveryService implements FeatureDiscoveryService {
+ constructor(private readonly config: RootConfigService) {}
+
+ async getBackendFeatures(): Promise<{ features: Array }> {
+ if (this.config.getOptionalString('backend.packages') !== 'all') {
+ return { features: [] };
+ }
+
+ const packageDir = await findClosestPackageDir(process.argv[1]);
+ if (!packageDir) {
+ throw new Error('Package discovery failed to find package.json');
+ }
+ const { dependencies } = require(resolvePath(
+ packageDir,
+ 'package.json',
+ )) as BackstagePackageJson;
+ const dependencyNames = Object.keys(dependencies || {});
+
+ const features: BackendFeature[] = [];
+
+ for (const name of dependencyNames) {
+ const depPkg = require(require.resolve(`${name}/package.json`, {
+ paths: [packageDir],
+ })) as BackstagePackageJson;
+ if (!LOADED_PACKAGE_ROLES.includes(depPkg?.backstage?.role ?? '')) {
+ continue;
+ }
+ const depModule = require(require.resolve(name, { paths: [packageDir] }));
+ for (const exportValue of Object.values(depModule)) {
+ if (isBackendFeature(exportValue)) {
+ features.push(exportValue);
+ }
+ if (isBackendFeatureFactory(exportValue)) {
+ features.push(exportValue());
+ }
+ }
+ }
+
+ return { features };
+ }
+}
+
+/** @alpha */
+export const featureDiscoveryServiceFactory = createServiceFactory({
+ service: featureDiscoveryServiceRef,
+ deps: {
+ config: coreServices.rootConfig,
+ },
+ factory({ config }) {
+ return new PackageDiscoveryService(config);
+ },
+});
+
+function isBackendFeature(value: unknown): value is BackendFeature {
+ return (
+ !!value &&
+ typeof value === 'object' &&
+ (value as BackendFeature).$$type === '@backstage/BackendFeature'
+ );
+}
+
+function isBackendFeatureFactory(
+ value: unknown,
+): value is () => BackendFeature {
+ return (
+ !!value &&
+ typeof value === 'function' &&
+ (value as any).$$type === '@backstage/BackendFeatureFactory'
+ );
+}
diff --git a/packages/backend-app-api/src/config/ObservableConfigProxy.ts b/packages/backend-app-api/src/config/ObservableConfigProxy.ts
index dd3334c9a5..8c8d3b553f 100644
--- a/packages/backend-app-api/src/config/ObservableConfigProxy.ts
+++ b/packages/backend-app-api/src/config/ObservableConfigProxy.ts
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-import { ConfigService } from '@backstage/backend-plugin-api';
-import { ConfigReader } from '@backstage/config';
+import { Config, ConfigReader } from '@backstage/config';
import { JsonValue } from '@backstage/types';
-export class ObservableConfigProxy implements ConfigService {
- private config: ConfigService = new ConfigReader({});
+export class ObservableConfigProxy implements Config {
+ private config: Config = new ConfigReader({});
private readonly subscribers: (() => void)[] = [];
@@ -32,7 +31,7 @@ export class ObservableConfigProxy implements ConfigService {
}
}
- setConfig(config: ConfigService) {
+ setConfig(config: Config) {
if (this.parent) {
throw new Error('immutable');
}
@@ -62,9 +61,9 @@ export class ObservableConfigProxy implements ConfigService {
};
}
- private select(required: true): ConfigService;
- private select(required: false): ConfigService | undefined;
- private select(required: boolean): ConfigService | undefined {
+ private select(required: true): Config;
+ private select(required: false): Config | undefined;
+ private select(required: boolean): Config | undefined {
if (this.parent && this.parentKey) {
if (required) {
return this.parent.select(true).getConfig(this.parentKey);
@@ -87,19 +86,19 @@ export class ObservableConfigProxy implements ConfigService {
getOptional(key?: string): T | undefined {
return this.select(false)?.getOptional(key);
}
- getConfig(key: string): ConfigService {
+ getConfig(key: string): Config {
return new ObservableConfigProxy(this, key);
}
- getOptionalConfig(key: string): ConfigService | undefined {
+ getOptionalConfig(key: string): Config | undefined {
if (this.select(false)?.has(key)) {
return new ObservableConfigProxy(this, key);
}
return undefined;
}
- getConfigArray(key: string): ConfigService[] {
+ getConfigArray(key: string): Config[] {
return this.select(true).getConfigArray(key);
}
- getOptionalConfigArray(key: string): ConfigService[] | undefined {
+ getOptionalConfigArray(key: string): Config[] | undefined {
return this.select(false)?.getOptionalConfigArray(key);
}
getNumber(key: string): number {
diff --git a/packages/backend-app-api/src/http/MiddlewareFactory.ts b/packages/backend-app-api/src/http/MiddlewareFactory.ts
index 774e784d1d..bd78d77045 100644
--- a/packages/backend-app-api/src/http/MiddlewareFactory.ts
+++ b/packages/backend-app-api/src/http/MiddlewareFactory.ts
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-import { ConfigService, LoggerService } from '@backstage/backend-plugin-api';
+import {
+ RootConfigService,
+ LoggerService,
+} from '@backstage/backend-plugin-api';
import {
Request,
Response,
@@ -47,7 +50,7 @@ import { NotImplementedError } from '@backstage/errors';
* @public
*/
export interface MiddlewareFactoryOptions {
- config: ConfigService;
+ config: RootConfigService;
logger: LoggerService;
}
@@ -78,7 +81,7 @@ export interface MiddlewareFactoryErrorOptions {
* @public
*/
export class MiddlewareFactory {
- #config: ConfigService;
+ #config: RootConfigService;
#logger: LoggerService;
/**
diff --git a/packages/backend-app-api/src/lib/DependencyGraph.test.ts b/packages/backend-app-api/src/lib/DependencyGraph.test.ts
new file mode 100644
index 0000000000..65efac0252
--- /dev/null
+++ b/packages/backend-app-api/src/lib/DependencyGraph.test.ts
@@ -0,0 +1,245 @@
+/*
+ * 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 { DependencyGraph } from './DependencyGraph';
+
+describe('DependencyGraph', () => {
+ it('should be empty', async () => {
+ const empty = DependencyGraph.fromMap({});
+ expect(empty.findUnsatisfiedDeps()).toEqual([]);
+ expect(empty.detectCircularDependency()).toBeUndefined();
+ await expect(
+ empty.parallelTopologicalTraversal(async id => id),
+ ).resolves.toEqual([]);
+ });
+
+ describe('detectCircularDependency', () => {
+ it('should return undefined with not deps', () => {
+ expect(
+ DependencyGraph.fromMap({
+ 1: {},
+ 2: {},
+ 3: {},
+ 4: {},
+ }).detectCircularDependency(),
+ ).toBeUndefined();
+ });
+
+ it('should return undefined with no circular deps', async () => {
+ expect(
+ DependencyGraph.fromMap({
+ 1: { provides: ['a'] },
+ 2: { consumes: ['a'], provides: ['b', 'c'] },
+ 3: { consumes: ['b'] },
+ 4: { consumes: ['c'] },
+ }).detectCircularDependency(),
+ ).toBeUndefined();
+ });
+
+ it('should detect an immediate circular dep', async () => {
+ expect(
+ DependencyGraph.fromMap({
+ 1: { provides: ['a'], consumes: ['a'] },
+ }).detectCircularDependency(),
+ ).toEqual(['1', '1']);
+ });
+
+ it('should detect a small circular dep', async () => {
+ expect(
+ DependencyGraph.fromMap({
+ 1: { provides: ['a'], consumes: ['b'] },
+ 2: { provides: ['b'], consumes: ['a'] },
+ }).detectCircularDependency(),
+ ).toEqual(['1', '2', '1']);
+ });
+
+ it('should detect a larger distant circular dep', async () => {
+ expect(
+ DependencyGraph.fromMap({
+ 1: { provides: ['a'] },
+ 2: { provides: ['b'], consumes: ['a', 'e'] },
+ 3: { provides: ['c'], consumes: ['b'] },
+ 4: { provides: ['d', 'e'], consumes: ['c', 'a'] },
+ }).detectCircularDependency(),
+ ).toEqual(['2', '3', '4', '2']);
+ });
+ });
+
+ describe('findUnsatisfiedDeps', () => {
+ it('should return nothing with no deps', () => {
+ expect(
+ DependencyGraph.fromMap({
+ 1: {},
+ 2: {},
+ 3: {},
+ 4: {},
+ }).findUnsatisfiedDeps(),
+ ).toEqual([]);
+ });
+
+ it('should return nothing when all deps are satisfied', async () => {
+ expect(
+ DependencyGraph.fromMap({
+ 1: { provides: ['a'] },
+ 2: { consumes: ['a'], provides: ['b', 'c'] },
+ 3: { consumes: ['b'] },
+ 4: { consumes: ['c'] },
+ }).findUnsatisfiedDeps(),
+ ).toEqual([]);
+ });
+
+ it('should find a single unsatisfied dep', async () => {
+ expect(
+ DependencyGraph.fromMap({
+ 1: { consumes: ['a'] },
+ }).findUnsatisfiedDeps(),
+ ).toEqual([{ value: '1', unsatisfied: ['a'] }]);
+ });
+
+ it('should handle circular dependencies', async () => {
+ expect(
+ DependencyGraph.fromMap({
+ 1: { consumes: ['a'], provides: ['a'] },
+ }).findUnsatisfiedDeps(),
+ ).toEqual([]);
+
+ expect(
+ DependencyGraph.fromMap({
+ 1: { consumes: ['a'], provides: ['b'] },
+ 2: { consumes: ['b'], provides: ['a'] },
+ }).findUnsatisfiedDeps(),
+ ).toEqual([]);
+
+ expect(
+ DependencyGraph.fromMap({
+ 1: { consumes: ['a'] },
+ 2: { consumes: ['b'], provides: ['c'] },
+ 3: { consumes: ['c'], provides: ['a', 'b'] },
+ }).findUnsatisfiedDeps(),
+ ).toEqual([]);
+ });
+
+ it('should find multiple unsatisfied deps for one node', async () => {
+ expect(
+ DependencyGraph.fromMap({
+ 1: { provides: ['a'], consumes: ['b'] },
+ 2: { provides: ['b'], consumes: ['a', 'd', 'e'] },
+ }).findUnsatisfiedDeps(),
+ ).toEqual([{ value: '2', unsatisfied: ['d', 'e'] }]);
+ });
+
+ it('should find multiple unsatisfied deps for multiple nodes', async () => {
+ expect(
+ DependencyGraph.fromMap({
+ 1: { provides: ['a'] },
+ 2: { provides: ['b'], consumes: ['a', 'd', 'e'] },
+ 3: { provides: [], consumes: ['b'] },
+ 4: { provides: [], consumes: ['c', 'a'] },
+ }).findUnsatisfiedDeps(),
+ ).toEqual([
+ { value: '2', unsatisfied: ['d', 'e'] },
+ { value: '4', unsatisfied: ['c'] },
+ ]);
+ });
+ });
+
+ describe('parallelTopologicalTraversal', () => {
+ it('should traverse with no deps', async () => {
+ await expect(
+ DependencyGraph.fromMap({
+ 1: {},
+ 2: {},
+ 3: {},
+ 4: {},
+ }).parallelTopologicalTraversal(async id => id),
+ ).resolves.toEqual(['1', '2', '3', '4']);
+ });
+
+ it('should traverse with a few deps', async () => {
+ await expect(
+ DependencyGraph.fromMap({
+ 1: { provides: ['a'] },
+ 2: { consumes: ['a'], provides: ['b', 'c'] },
+ 3: { consumes: ['b'] },
+ 4: { consumes: ['c'] },
+ }).parallelTopologicalTraversal(async id => id),
+ ).resolves.toEqual(['1', '2', '3', '4']);
+ });
+
+ it('should traverse in reverse', async () => {
+ await expect(
+ DependencyGraph.fromMap({
+ 1: { consumes: ['c'] },
+ 2: { provides: ['c'], consumes: ['b'] },
+ 3: { provides: ['b'], consumes: ['a'] },
+ 4: { provides: ['a'] },
+ }).parallelTopologicalTraversal(async id => id),
+ ).resolves.toEqual(['4', '3', '2', '1']);
+ });
+
+ it('should execute in parallel', async () => {
+ await expect(
+ DependencyGraph.fromMap({
+ 1: { provides: ['a'] },
+ 2: { provides: ['b'], consumes: ['a'] },
+ 3: { provides: ['c'], consumes: ['a'] },
+ 4: { consumes: ['b'] },
+ 5: { consumes: ['c'] },
+ }).parallelTopologicalTraversal(async id => id),
+ ).resolves.toEqual(['1', '2', '3', '4', '5']);
+
+ // Same as above, but with 2 being delayed
+ await expect(
+ DependencyGraph.fromMap({
+ 1: { provides: ['a'] },
+ 2: { provides: ['b'], consumes: ['a'] },
+ 3: { provides: ['c'], consumes: ['a'] },
+ 4: { consumes: ['b'] },
+ 5: { consumes: ['c'] },
+ }).parallelTopologicalTraversal(async id => {
+ // When delaying 2 we expect 3 and 5 to complete before 2 and 4
+ if (id === '2') {
+ await new Promise(resolve => setTimeout(resolve, 100));
+ }
+ return id;
+ }),
+ ).resolves.toEqual(['1', '3', '5', '2', '4']);
+ });
+
+ it('should detect circular dependencies', async () => {
+ await expect(
+ DependencyGraph.fromMap({
+ 1: { provides: ['a'], consumes: ['a'] },
+ }).parallelTopologicalTraversal(async id => id),
+ ).rejects.toThrow('Circular dependency detected');
+
+ await expect(
+ DependencyGraph.fromMap({
+ 1: { provides: ['a'], consumes: ['b'] },
+ 2: { provides: ['b'], consumes: ['a'] },
+ }).parallelTopologicalTraversal(async id => id),
+ ).rejects.toThrow('Circular dependency detected');
+
+ await expect(
+ DependencyGraph.fromMap({
+ 1: { provides: ['a'] },
+ 2: { provides: ['c'], consumes: ['a', 'b'] },
+ 3: { provides: ['b'], consumes: ['a', 'c'] },
+ }).parallelTopologicalTraversal(async id => id),
+ ).rejects.toThrow('Circular dependency detected');
+ });
+ });
+});
diff --git a/packages/backend-app-api/src/lib/DependencyGraph.ts b/packages/backend-app-api/src/lib/DependencyGraph.ts
new file mode 100644
index 0000000000..8fd679f555
--- /dev/null
+++ b/packages/backend-app-api/src/lib/DependencyGraph.ts
@@ -0,0 +1,197 @@
+/*
+ * 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.
+ */
+
+interface NodeInput {
+ value: T;
+ consumes?: Iterable;
+ provides?: Iterable;
+}
+
+/** @internal */
+class Node {
+ static from(input: NodeInput) {
+ return new Node(
+ input.value,
+ input.consumes ? new Set(input.consumes) : new Set(),
+ input.provides ? new Set(input.provides) : new Set(),
+ );
+ }
+
+ private constructor(
+ readonly value: T,
+ readonly consumes: Set,
+ readonly provides: Set,
+ ) {}
+}
+
+/**
+ * Internal helper to help validate and traverse a dependency graph.
+ * @internal
+ */
+export class DependencyGraph {
+ static fromMap(
+ nodes: Record, 'value'>>,
+ ): DependencyGraph {
+ return this.fromIterable(
+ Object.entries(nodes).map(([key, node]) => ({
+ value: String(key),
+ ...node,
+ })),
+ );
+ }
+
+ static fromIterable(
+ nodeInputs: Iterable>,
+ ): DependencyGraph {
+ const nodes = new Array>();
+ for (const nodeInput of nodeInputs) {
+ nodes.push(Node.from(nodeInput));
+ }
+
+ return new DependencyGraph(nodes);
+ }
+
+ #nodes: Array>;
+ #allProvided: Set;
+
+ private constructor(nodes: Array>) {
+ this.#nodes = nodes;
+ this.#allProvided = new Set();
+
+ for (const node of this.#nodes.values()) {
+ for (const produced of node.provides) {
+ this.#allProvided.add(produced);
+ }
+ }
+ }
+
+ // Find all nodes that consume dependencies that are not provided by any other node
+ findUnsatisfiedDeps(): Array<{ value: T; unsatisfied: string[] }> {
+ const unsatisfiedDependencies = [];
+ for (const node of this.#nodes.values()) {
+ const unsatisfied = Array.from(node.consumes).filter(
+ id => !this.#allProvided.has(id),
+ );
+ if (unsatisfied.length > 0) {
+ unsatisfiedDependencies.push({ value: node.value, unsatisfied });
+ }
+ }
+ return unsatisfiedDependencies;
+ }
+
+ // Detect circular dependencies within the graph, returning the path of nodes that
+ // form a cycle, with the same node as the first and last element of the array.
+ detectCircularDependency(): T[] | undefined {
+ for (const startNode of this.#nodes) {
+ const visited = new Set>();
+ const stack = new Array<[node: Node, path: T[]]>([
+ startNode,
+ [startNode.value],
+ ]);
+
+ while (stack.length > 0) {
+ const [node, path] = stack.pop()!;
+ if (visited.has(node)) {
+ continue;
+ }
+ visited.add(node);
+ for (const produced of node.provides) {
+ const consumerNodes = this.#nodes.filter(other =>
+ other.consumes.has(produced),
+ );
+ for (const consumer of consumerNodes) {
+ if (consumer === startNode) {
+ return [...path, startNode.value];
+ }
+ if (!visited.has(consumer)) {
+ stack.push([consumer, [...path, consumer.value]]);
+ }
+ }
+ }
+ }
+ }
+ return undefined;
+ }
+
+ /**
+ * Traverses the dependency graph in topological order, calling the provided
+ * function for each node and waiting for it to resolve.
+ *
+ * The nodes are traversed in parallel, but in such a way that no node is
+ * visited before all of its dependencies.
+ *
+ * Dependencies of nodes that are not produced by any other nodes will be ignored.
+ */
+ async parallelTopologicalTraversal(
+ fn: (value: T) => Promise,
+ ): Promise {
+ const allProvided = this.#allProvided;
+ const producedSoFar = new Set();
+ const waiting = new Set(this.#nodes.values());
+ const visited = new Set>();
+ const results = new Array();
+ let inFlight = 0; // Keep track of how many callbacks are in flight, so that we know if we got stuck
+
+ // Find all nodes that have no dependencies that have not already been produced by visited nodes
+ async function processMoreNodes() {
+ if (waiting.size === 0) {
+ return;
+ }
+ const nodesToProcess = [];
+ for (const node of waiting) {
+ let ready = true;
+ for (const consumed of node.consumes) {
+ if (allProvided.has(consumed) && !producedSoFar.has(consumed)) {
+ ready = false;
+ continue;
+ }
+ }
+ if (ready) {
+ nodesToProcess.push(node);
+ }
+ }
+
+ for (const node of nodesToProcess) {
+ waiting.delete(node);
+ }
+
+ if (nodesToProcess.length === 0 && inFlight === 0) {
+ // We expect the caller to check for circular dependencies before
+ // traversal, so this error should never happen
+ throw new Error('Circular dependency detected');
+ }
+
+ await Promise.all(nodesToProcess.map(processNode));
+ }
+
+ // Process an individual node, and then add its produced dependencies to the set of available products
+ async function processNode(node: Node) {
+ visited.add(node);
+ inFlight += 1;
+
+ const result = await fn(node.value);
+ results.push(result);
+
+ node.provides.forEach(produced => producedSoFar.add(produced));
+ inFlight -= 1;
+ await processMoreNodes();
+ }
+
+ await processMoreNodes();
+
+ return results;
+ }
+}
diff --git a/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts b/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts
index 2208249280..b91356a13a 100644
--- a/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts
+++ b/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts
@@ -24,7 +24,7 @@ import {
export const cacheServiceFactory = createServiceFactory({
service: coreServices.cache,
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
plugin: coreServices.pluginMetadata,
},
async createRootContext({ config }) {
diff --git a/packages/backend-app-api/src/services/implementations/config/index.ts b/packages/backend-app-api/src/services/implementations/config/index.ts
index 690f1e2d36..1775ef2efc 100644
--- a/packages/backend-app-api/src/services/implementations/config/index.ts
+++ b/packages/backend-app-api/src/services/implementations/config/index.ts
@@ -14,5 +14,5 @@
* limitations under the License.
*/
-export { configServiceFactory } from './configServiceFactory';
-export type { ConfigFactoryOptions } from './configServiceFactory';
+export { rootConfigServiceFactory } from './rootConfigServiceFactory';
+export type { RootConfigFactoryOptions } from './rootConfigServiceFactory';
diff --git a/packages/backend-app-api/src/services/implementations/config/configServiceFactory.ts b/packages/backend-app-api/src/services/implementations/config/rootConfigServiceFactory.ts
similarity index 87%
rename from packages/backend-app-api/src/services/implementations/config/configServiceFactory.ts
rename to packages/backend-app-api/src/services/implementations/config/rootConfigServiceFactory.ts
index d3b29ce545..1fa8d792ad 100644
--- a/packages/backend-app-api/src/services/implementations/config/configServiceFactory.ts
+++ b/packages/backend-app-api/src/services/implementations/config/rootConfigServiceFactory.ts
@@ -24,7 +24,7 @@ import {
} from '@backstage/config-loader';
/** @public */
-export interface ConfigFactoryOptions {
+export interface RootConfigFactoryOptions {
/**
* Process arguments to use instead of the default `process.argv()`.
*/
@@ -37,9 +37,9 @@ export interface ConfigFactoryOptions {
}
/** @public */
-export const configServiceFactory = createServiceFactory(
- (options?: ConfigFactoryOptions) => ({
- service: coreServices.config,
+export const rootConfigServiceFactory = createServiceFactory(
+ (options?: RootConfigFactoryOptions) => ({
+ service: coreServices.rootConfig,
deps: {},
async factory() {
const source = ConfigSources.default({
diff --git a/packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts b/packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts
index 05e2f1deb9..139609b6c1 100644
--- a/packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts
+++ b/packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts
@@ -25,7 +25,7 @@ import { ConfigReader } from '@backstage/config';
export const databaseServiceFactory = createServiceFactory({
service: coreServices.database,
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
lifecycle: coreServices.lifecycle,
pluginMetadata: coreServices.pluginMetadata,
},
diff --git a/packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts b/packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts
index 7e2bb6b21e..6bdc4b4856 100644
--- a/packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts
+++ b/packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts
@@ -24,7 +24,7 @@ import {
export const discoveryServiceFactory = createServiceFactory({
service: coreServices.discovery,
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
},
async factory({ config }) {
return HostDiscovery.fromConfig(config);
diff --git a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts b/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts
index 38cba73eda..3e5b7da9dd 100644
--- a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts
+++ b/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts
@@ -24,7 +24,7 @@ import { ServerPermissionClient } from '@backstage/plugin-permission-node';
export const permissionsServiceFactory = createServiceFactory({
service: coreServices.permissions,
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
discovery: coreServices.discovery,
tokenManager: coreServices.tokenManager,
},
diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterServiceFactory.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterServiceFactory.ts
index 5eaf8aa152..4c410e25ea 100644
--- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterServiceFactory.ts
+++ b/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterServiceFactory.ts
@@ -15,7 +15,7 @@
*/
import {
- ConfigService,
+ RootConfigService,
coreServices,
createServiceFactory,
LifecycleService,
@@ -36,7 +36,7 @@ export interface RootHttpRouterConfigureContext {
app: Express;
middleware: MiddlewareFactory;
routes: RequestHandler;
- config: ConfigService;
+ config: RootConfigService;
logger: LoggerService;
lifecycle: LifecycleService;
}
@@ -70,7 +70,7 @@ export const rootHttpRouterServiceFactory = createServiceFactory(
(options?: RootHttpRouterFactoryOptions) => ({
service: coreServices.rootHttpRouter,
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
rootLogger: coreServices.rootLogger,
lifecycle: coreServices.rootLifecycle,
},
diff --git a/packages/backend-app-api/src/services/implementations/rootLogger/rootLoggerServiceFactory.ts b/packages/backend-app-api/src/services/implementations/rootLogger/rootLoggerServiceFactory.ts
index 11bbd58050..bd45df383f 100644
--- a/packages/backend-app-api/src/services/implementations/rootLogger/rootLoggerServiceFactory.ts
+++ b/packages/backend-app-api/src/services/implementations/rootLogger/rootLoggerServiceFactory.ts
@@ -26,7 +26,7 @@ import { createConfigSecretEnumerator } from '../../../config';
export const rootLoggerServiceFactory = createServiceFactory({
service: coreServices.rootLogger,
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
},
async factory({ config }) {
const logger = WinstonLogger.create({
diff --git a/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.test.ts b/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.test.ts
index 7af9de44da..d771c5dd11 100644
--- a/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.test.ts
+++ b/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.test.ts
@@ -61,8 +61,7 @@ describe('schedulerFactory', () => {
});
await startTestBackend({
- features: [plugin()],
- services: [subject],
+ features: [plugin(), subject],
});
});
});
diff --git a/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.ts b/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.ts
index b3726176b6..7417c18e23 100644
--- a/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.ts
+++ b/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.ts
@@ -24,7 +24,7 @@ import { ServerTokenManager } from '@backstage/backend-common';
export const tokenManagerServiceFactory = createServiceFactory({
service: coreServices.tokenManager,
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
logger: coreServices.rootLogger,
},
createRootContext({ config, logger }) {
diff --git a/packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts b/packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts
index 83e594aa0b..6da71ac0fc 100644
--- a/packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts
+++ b/packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts
@@ -24,7 +24,7 @@ import {
export const urlReaderServiceFactory = createServiceFactory({
service: coreServices.urlReader,
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
logger: coreServices.logger,
},
async factory({ config, logger }) {
diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts
index b05194f594..791b9b1c3a 100644
--- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts
+++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts
@@ -20,10 +20,15 @@ import {
coreServices,
createBackendPlugin,
createBackendModule,
+ createExtensionPoint,
} from '@backstage/backend-plugin-api';
import { BackendInitializer } from './BackendInitializer';
-import { ServiceRegistry } from './ServiceRegistry';
-import { rootLifecycleServiceFactory } from '../services/implementations';
+
+import {
+ lifecycleServiceFactory,
+ loggerServiceFactory,
+ rootLifecycleServiceFactory,
+} from '../services/implementations';
const rootRef = createServiceRef<{ x: number }>({
id: '1',
@@ -44,12 +49,23 @@ class MockLogger {
}
}
+const baseFactories = [
+ lifecycleServiceFactory(),
+ rootLifecycleServiceFactory(),
+ createServiceFactory({
+ service: coreServices.rootLogger,
+ deps: {},
+ factory: () => new MockLogger(),
+ })(),
+ loggerServiceFactory(),
+];
+
describe('BackendInitializer', () => {
it('should initialize root scoped services', async () => {
const rootFactory = jest.fn();
const pluginFactory = jest.fn();
- const registry = new ServiceRegistry([
+ const services = [
createServiceFactory({
service: rootRef,
deps: {},
@@ -66,17 +82,76 @@ describe('BackendInitializer', () => {
deps: {},
factory: () => new MockLogger(),
})(),
- ]);
+ ];
- const init = new BackendInitializer(registry);
+ const init = new BackendInitializer(services);
await init.start();
expect(rootFactory).toHaveBeenCalled();
expect(pluginFactory).not.toHaveBeenCalled();
});
+ it('should initialize modules with extension points', async () => {
+ expect.assertions(3);
+
+ const extensionPoint = createExtensionPoint<{ values: string[] }>({
+ id: 'a',
+ });
+ const init = new BackendInitializer(baseFactories);
+
+ init.add(
+ createBackendModule({
+ pluginId: 'test',
+ moduleId: 'modA',
+ register(reg) {
+ reg.registerInit({
+ deps: { extension: extensionPoint },
+ async init({ extension }) {
+ expect(extension.values).toEqual(['b']);
+ extension.values.push('a');
+ },
+ });
+ },
+ })(),
+ );
+
+ init.add(
+ createBackendModule({
+ pluginId: 'test',
+ moduleId: 'modB',
+ register(reg) {
+ const values = ['b'];
+ reg.registerExtensionPoint(extensionPoint, { values });
+ reg.registerInit({
+ deps: {},
+ async init() {
+ expect(values).toEqual(['b', 'a', 'c']);
+ },
+ });
+ },
+ })(),
+ );
+
+ init.add(
+ createBackendModule({
+ pluginId: 'test',
+ moduleId: 'modC',
+ register(reg) {
+ reg.registerInit({
+ deps: { extension: extensionPoint },
+ async init({ extension }) {
+ expect(extension.values).toEqual(['b', 'a']);
+ extension.values.push('c');
+ },
+ });
+ },
+ })(),
+ );
+ await init.start();
+ });
+
it('should forward errors when plugins fail to start', async () => {
- const init = new BackendInitializer(new ServiceRegistry([]));
+ const init = new BackendInitializer([]);
init.add(
createBackendPlugin({
pluginId: 'test',
@@ -96,7 +171,7 @@ describe('BackendInitializer', () => {
});
it('should forward errors when modules fail to start', async () => {
- const init = new BackendInitializer(new ServiceRegistry([]));
+ const init = new BackendInitializer([]);
init.add(
createBackendModule({
pluginId: 'test',
@@ -117,7 +192,7 @@ describe('BackendInitializer', () => {
});
it('should reject duplicate plugins', async () => {
- const init = new BackendInitializer(new ServiceRegistry([]));
+ const init = new BackendInitializer([]);
init.add(
createBackendPlugin({
pluginId: 'test',
@@ -146,7 +221,7 @@ describe('BackendInitializer', () => {
});
it('should reject duplicate modules', async () => {
- const init = new BackendInitializer(new ServiceRegistry([]));
+ const init = new BackendInitializer([]);
init.add(
createBackendModule({
pluginId: 'test',
@@ -175,4 +250,78 @@ describe('BackendInitializer', () => {
"Module 'mod' for plugin 'test' is already registered",
);
});
+
+ it('should reject modules with circular dependencies', async () => {
+ const extA = createExtensionPoint({ id: 'a' });
+ const extB = createExtensionPoint({ id: 'b' });
+ const init = new BackendInitializer([
+ rootLifecycleServiceFactory(),
+ createServiceFactory({
+ service: coreServices.rootLogger,
+ deps: {},
+ factory: () => new MockLogger(),
+ })(),
+ ]);
+ init.add(
+ createBackendModule({
+ pluginId: 'test',
+ moduleId: 'modA',
+ register(reg) {
+ reg.registerExtensionPoint(extA, 'a');
+ reg.registerInit({
+ deps: { ext: extB },
+ async init() {},
+ });
+ },
+ })(),
+ );
+ init.add(
+ createBackendModule({
+ pluginId: 'test',
+ moduleId: 'modB',
+ register(reg) {
+ reg.registerExtensionPoint(extB, 'b');
+ reg.registerInit({
+ deps: { ext: extA },
+ async init() {},
+ });
+ },
+ })(),
+ );
+ await expect(init.start()).rejects.toThrow(
+ "Circular dependency detected for modules of plugin 'test', 'modA' -> 'modB' -> 'modA'",
+ );
+ });
+
+ it('should reject modules that depend on extension points other plugins', async () => {
+ const init = new BackendInitializer(baseFactories);
+ const extA = createExtensionPoint({ id: 'a' });
+ init.add(
+ createBackendPlugin({
+ pluginId: 'testA',
+ register(reg) {
+ reg.registerExtensionPoint(extA, 'a');
+ reg.registerInit({
+ deps: {},
+ async init() {},
+ });
+ },
+ })(),
+ );
+ init.add(
+ createBackendModule({
+ pluginId: 'testB',
+ moduleId: 'mod',
+ register(reg) {
+ reg.registerInit({
+ deps: { ext: extA },
+ async init() {},
+ });
+ },
+ })(),
+ );
+ await expect(init.start()).rejects.toThrow(
+ "Extension point registered for plugin 'testA' may not be used by module for plugin 'testB'",
+ );
+ });
});
diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts
index 8c2b75e887..93d2f6942a 100644
--- a/packages/backend-app-api/src/wiring/BackendInitializer.ts
+++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts
@@ -19,6 +19,7 @@ import {
ExtensionPoint,
coreServices,
ServiceRef,
+ ServiceFactory,
} from '@backstage/backend-plugin-api';
import { BackendLifecycleImpl } from '../services/implementations/rootLifecycle/rootLifecycleServiceFactory';
import { BackendPluginLifecycleImpl } from '../services/implementations/lifecycle/lifecycleServiceFactory';
@@ -26,7 +27,10 @@ import { EnumerableServiceHolder, ServiceOrExtensionPoint } from './types';
// Direct internal import to avoid duplication
// eslint-disable-next-line @backstage/no-forbidden-package-imports
import { InternalBackendFeature } from '@backstage/backend-plugin-api/src/wiring/types';
-import { ForwardedError } from '@backstage/errors';
+import { ForwardedError, ConflictError } from '@backstage/errors';
+import { featureDiscoveryServiceRef } from '@backstage/backend-plugin-api/alpha';
+import { DependencyGraph } from '../lib/DependencyGraph';
+import { ServiceRegistry } from './ServiceRegistry';
export interface BackendRegisterInit {
consumes: Set;
@@ -40,11 +44,16 @@ export interface BackendRegisterInit {
export class BackendInitializer {
#startPromise?: Promise;
#features = new Array();
- #extensionPoints = new Map, unknown>();
- #serviceHolder: EnumerableServiceHolder;
+ #extensionPoints = new Map<
+ ExtensionPoint,
+ { impl: unknown; pluginId: string }
+ >();
+ #serviceHolder: EnumerableServiceHolder | undefined;
+ #providedServiceFactories = new Array();
+ #defaultApiFactories: ServiceFactory[];
- constructor(serviceHolder: EnumerableServiceHolder) {
- this.#serviceHolder = serviceHolder;
+ constructor(defaultApiFactories: ServiceFactory[]) {
+ this.#defaultApiFactories = defaultApiFactories;
}
async #getInitDeps(
@@ -55,13 +64,16 @@ export class BackendInitializer {
const missingRefs = new Set();
for (const [name, ref] of Object.entries(deps)) {
- const extensionPoint = this.#extensionPoints.get(
- ref as ExtensionPoint,
- );
- if (extensionPoint) {
- result.set(name, extensionPoint);
+ const ep = this.#extensionPoints.get(ref as ExtensionPoint);
+ if (ep) {
+ if (ep.pluginId !== pluginId) {
+ throw new Error(
+ `Extension point registered for plugin '${ep.pluginId}' may not be used by module for plugin '${pluginId}'`,
+ );
+ }
+ result.set(name, ep.impl);
} else {
- const impl = await this.#serviceHolder.get(
+ const impl = await this.#serviceHolder!.get(
ref as ServiceRef,
pluginId,
);
@@ -87,18 +99,44 @@ export class BackendInitializer {
if (this.#startPromise) {
throw new Error('feature can not be added after the backend has started');
}
+ this.#addFeature(feature);
+ }
+
+ #addFeature(feature: BackendFeature) {
if (feature.$$type !== '@backstage/BackendFeature') {
throw new Error(
`Failed to add feature, invalid type '${feature.$$type}'`,
);
}
- const internalFeature = feature as InternalBackendFeature;
- if (internalFeature.version !== 'v1') {
+
+ if (isServiceFactory(feature)) {
+ if (feature.service.id === coreServices.pluginMetadata.id) {
+ throw new Error(
+ `The ${coreServices.pluginMetadata.id} service cannot be overridden`,
+ );
+ }
+ if (
+ this.#providedServiceFactories.find(
+ sf => sf.service.id === feature.service.id,
+ )
+ ) {
+ throw new Error(
+ `Duplicate service implementations provided for ${feature.service.id}`,
+ );
+ }
+ this.#providedServiceFactories.push(feature);
+ } else if (isInternalBackendFeature(feature)) {
+ if (feature.version !== 'v1') {
+ throw new Error(
+ `Failed to add feature, invalid version '${feature.version}'`,
+ );
+ }
+ this.#features.push(feature);
+ } else {
throw new Error(
- `Failed to add feature, invalid version '${internalFeature.version}'`,
+ `Failed to add feature, invalid feature ${JSON.stringify(feature)}`,
);
}
- this.#features.push(internalFeature);
}
async start(): Promise {
@@ -129,6 +167,23 @@ export class BackendInitializer {
}
async #doStart(): Promise {
+ this.#serviceHolder = new ServiceRegistry([
+ ...this.#defaultApiFactories,
+ ...this.#providedServiceFactories,
+ ]);
+
+ const featureDiscovery = await this.#serviceHolder.get(
+ featureDiscoveryServiceRef,
+ 'root',
+ );
+
+ if (featureDiscovery) {
+ const { features } = await featureDiscovery.getBackendFeatures();
+ for (const feature of features) {
+ this.#addFeature(feature);
+ }
+ }
+
// Initialize all root scoped services
for (const ref of this.#serviceHolder.getServiceRefs()) {
if (ref.scope === 'root') {
@@ -144,14 +199,17 @@ export class BackendInitializer {
for (const r of feature.getRegistrations()) {
const provides = new Set>();
- if (r.type === 'plugin') {
+ if (r.type === 'plugin' || r.type === 'module') {
for (const [extRef, extImpl] of r.extensionPoints) {
if (this.#extensionPoints.has(extRef)) {
throw new Error(
`ExtensionPoint with ID '${extRef.id}' is already registered`,
);
}
- this.#extensionPoints.set(extRef, extImpl);
+ this.#extensionPoints.set(extRef, {
+ impl: extImpl,
+ pluginId: r.pluginId,
+ });
provides.add(extRef);
}
}
@@ -193,21 +251,41 @@ export class BackendInitializer {
await Promise.all(
allPluginIds.map(async pluginId => {
// Modules are initialized before plugins, so that they can provide extension to the plugin
- const modules = moduleInits.get(pluginId) ?? [];
- await Promise.all(
- Array.from(modules).map(async ([moduleId, moduleInit]) => {
- const moduleDeps = await this.#getInitDeps(
- moduleInit.init.deps,
- pluginId,
+ const modules = moduleInits.get(pluginId);
+ if (modules) {
+ const tree = DependencyGraph.fromIterable(
+ Array.from(modules).map(([moduleId, moduleInit]) => ({
+ value: { moduleId, moduleInit },
+ // Relationships are reversed at this point since we're only interested in the extension points.
+ // If a modules provides extension point A we want it to be initialized AFTER all modules
+ // that depend on extension point A, so that they can provide their extensions.
+ consumes: Array.from(moduleInit.provides).map(p => p.id),
+ provides: Array.from(moduleInit.consumes).map(c => c.id),
+ })),
+ );
+ const circular = tree.detectCircularDependency();
+ if (circular) {
+ throw new ConflictError(
+ `Circular dependency detected for modules of plugin '${pluginId}', ${circular
+ .map(({ moduleId }) => `'${moduleId}'`)
+ .join(' -> ')}`,
);
- await moduleInit.init.func(moduleDeps).catch(error => {
- throw new ForwardedError(
- `Module '${moduleId}' for plugin '${pluginId}' startup failed`,
- error,
+ }
+ await tree.parallelTopologicalTraversal(
+ async ({ moduleId, moduleInit }) => {
+ const moduleDeps = await this.#getInitDeps(
+ moduleInit.init.deps,
+ pluginId,
);
- });
- }),
- );
+ await moduleInit.init.func(moduleDeps).catch(error => {
+ throw new ForwardedError(
+ `Module '${moduleId}' for plugin '${pluginId}' startup failed`,
+ error,
+ );
+ });
+ },
+ );
+ }
// Once all modules have been initialized, we can initialize the plugin itself
const pluginInit = pluginInits.get(pluginId);
@@ -259,7 +337,12 @@ export class BackendInitializer {
if (!this.#startPromise) {
return;
}
- await this.#startPromise;
+
+ try {
+ await this.#startPromise;
+ } catch (error) {
+ // The startup failed, but we may still want to do cleanup so we continue silently
+ }
const lifecycleService = await this.#getRootLifecycleImpl();
await lifecycleService.shutdown();
@@ -267,7 +350,7 @@ export class BackendInitializer {
// Bit of a hacky way to grab the lifecycle services, potentially find a nicer way to do this
async #getRootLifecycleImpl(): Promise {
- const lifecycleService = await this.#serviceHolder.get(
+ const lifecycleService = await this.#serviceHolder!.get(
coreServices.rootLifecycle,
'root',
);
@@ -280,7 +363,7 @@ export class BackendInitializer {
async #getPluginLifecycleImpl(
pluginId: string,
): Promise {
- const lifecycleService = await this.#serviceHolder.get(
+ const lifecycleService = await this.#serviceHolder!.get(
coreServices.lifecycle,
pluginId,
);
@@ -290,3 +373,15 @@ export class BackendInitializer {
throw new Error('Unexpected plugin lifecycle service implementation');
}
}
+
+function isServiceFactory(feature: BackendFeature): feature is ServiceFactory {
+ return !!(feature as ServiceFactory).service;
+}
+
+function isInternalBackendFeature(
+ feature: BackendFeature,
+): feature is InternalBackendFeature {
+ return (
+ typeof (feature as InternalBackendFeature).getRegistrations === 'function'
+ );
+}
diff --git a/packages/backend-app-api/src/wiring/BackstageBackend.ts b/packages/backend-app-api/src/wiring/BackstageBackend.ts
index c53f47b685..0ea55bc1c7 100644
--- a/packages/backend-app-api/src/wiring/BackstageBackend.ts
+++ b/packages/backend-app-api/src/wiring/BackstageBackend.ts
@@ -14,22 +14,19 @@
* limitations under the License.
*/
-import { ServiceFactory, BackendFeature } from '@backstage/backend-plugin-api';
+import { BackendFeature, ServiceFactory } from '@backstage/backend-plugin-api';
import { BackendInitializer } from './BackendInitializer';
-import { ServiceRegistry } from './ServiceRegistry';
import { Backend } from './types';
export class BackstageBackend implements Backend {
- #services: ServiceRegistry;
#initializer: BackendInitializer;
- constructor(apiFactories: ServiceFactory[]) {
- this.#services = new ServiceRegistry(apiFactories);
- this.#initializer = new BackendInitializer(this.#services);
+ constructor(defaultServiceFactories: ServiceFactory[]) {
+ this.#initializer = new BackendInitializer(defaultServiceFactories);
}
- add(feature: BackendFeature): void {
- this.#initializer.add(feature);
+ add(feature: BackendFeature | (() => BackendFeature)): void {
+ this.#initializer.add(typeof feature === 'function' ? feature() : feature);
}
async start(): Promise {
diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts
index e698ade348..b60a24c8d8 100644
--- a/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts
+++ b/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts
@@ -31,7 +31,7 @@ const sf1 = createServiceFactory({
async factory() {
return { x: 1 };
},
-})();
+});
const ref2 = createServiceRef<{ x: number }>({
scope: 'root',
@@ -43,14 +43,14 @@ const sf2 = createServiceFactory({
async factory() {
return { x: 2 };
},
-})();
+});
const sf2b = createServiceFactory({
service: ref2,
deps: {},
async factory() {
return { x: 22 };
},
-})();
+});
const refDefault1 = createServiceRef<{ x: number }>({
id: '1',
@@ -61,7 +61,7 @@ const refDefault1 = createServiceRef<{ x: number }>({
async factory() {
return { x: 10 };
},
- })(),
+ }),
});
const refDefault2a = createServiceRef<{ x: number }>({
@@ -95,7 +95,7 @@ describe('ServiceRegistry', () => {
});
it('should return an implementation for a registered ref', async () => {
- const registry = new ServiceRegistry([sf1]);
+ const registry = new ServiceRegistry([sf1()]);
await expect(registry.get(ref1, 'catalog')).resolves.toEqual({ x: 1 });
await expect(registry.get(ref1, 'scaffolder')).resolves.toEqual({ x: 1 });
expect(await registry.get(ref1, 'catalog')).toBe(
@@ -110,7 +110,7 @@ describe('ServiceRegistry', () => {
});
it('should handle multiple factories with different serviceRefs', async () => {
- const registry = new ServiceRegistry([sf1, sf2]);
+ const registry = new ServiceRegistry([sf1(), sf2()]);
await expect(registry.get(ref1, 'catalog')).resolves.toEqual({
x: 1,
@@ -131,7 +131,7 @@ describe('ServiceRegistry', () => {
return { x: 2 };
},
});
- const registry = new ServiceRegistry([factory(), sf1]);
+ const registry = new ServiceRegistry([factory(), sf1()]);
await expect(registry.get(ref2, 'catalog')).rejects.toThrow(
"Failed to instantiate 'root' scoped service '2' because it depends on 'plugin' scoped service '1'.",
);
@@ -145,7 +145,7 @@ describe('ServiceRegistry', () => {
return { x: rootDep.x };
},
});
- const registry = new ServiceRegistry([factory(), sf2]);
+ const registry = new ServiceRegistry([factory(), sf2()]);
await expect(registry.get(ref1, 'catalog')).resolves.toEqual({
x: 2,
});
@@ -160,7 +160,7 @@ describe('ServiceRegistry', () => {
return { x: rootDep.x };
},
});
- const registry = new ServiceRegistry([factory(), sf2]);
+ const registry = new ServiceRegistry([factory(), sf2()]);
await expect(registry.get(ref, 'catalog')).resolves.toEqual({
x: 2,
});
@@ -182,7 +182,7 @@ describe('ServiceRegistry', () => {
});
it('should use the last factory for each ref', async () => {
- const registry = new ServiceRegistry([sf2, sf2b]);
+ const registry = new ServiceRegistry([sf2(), sf2b()]);
await expect(registry.get(ref2, 'catalog')).resolves.toEqual({
x: 22,
});
@@ -196,7 +196,7 @@ describe('ServiceRegistry', () => {
});
it('should not use the defaultFactory from the ref if provided to the registry', async () => {
- const registry = new ServiceRegistry([sf1]);
+ const registry = new ServiceRegistry([sf1()]);
await expect(registry.get(refDefault1, 'catalog')).resolves.toEqual({
x: 1,
});
diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.ts
index b5b55f67cf..b6de05b361 100644
--- a/packages/backend-app-api/src/wiring/ServiceRegistry.ts
+++ b/packages/backend-app-api/src/wiring/ServiceRegistry.ts
@@ -39,7 +39,7 @@ function toInternalServiceFactory(
factory: ServiceFactory,
): InternalServiceFactory {
const f = factory as InternalServiceFactory;
- if (f.$$type !== '@backstage/ServiceFactory') {
+ if (f.$$type !== '@backstage/BackendFeature') {
throw new Error(`Invalid service factory, bad type '${f.$$type}'`);
}
if (f.version !== 'v1') {
@@ -49,10 +49,10 @@ function toInternalServiceFactory(
}
const pluginMetadataServiceFactory = createServiceFactory(
- (options: { pluginId: string }) => ({
+ (options?: { pluginId: string }) => ({
service: coreServices.pluginMetadata,
deps: {},
- factory: async () => ({ getId: () => options.pluginId }),
+ factory: async () => ({ getId: () => options?.pluginId! }),
}),
);
diff --git a/packages/backend-app-api/src/wiring/createSpecializedBackend.test.ts b/packages/backend-app-api/src/wiring/createSpecializedBackend.test.ts
index dc9a0cc085..1fd7fcbbec 100644
--- a/packages/backend-app-api/src/wiring/createSpecializedBackend.test.ts
+++ b/packages/backend-app-api/src/wiring/createSpecializedBackend.test.ts
@@ -22,13 +22,15 @@ import { createSpecializedBackend } from './createSpecializedBackend';
describe('createSpecializedBackend', () => {
it('should create a backend without services', () => {
- expect(() => createSpecializedBackend({ services: [] })).not.toThrow();
+ expect(() =>
+ createSpecializedBackend({ defaultServiceFactories: [] }),
+ ).not.toThrow();
});
it('should throw on duplicate service implementations', () => {
expect(() =>
createSpecializedBackend({
- services: [
+ defaultServiceFactories: [
createServiceFactory({
service: coreServices.rootLifecycle,
deps: {},
@@ -55,7 +57,7 @@ describe('createSpecializedBackend', () => {
it('should throw when providing a plugin metadata service implementation', () => {
expect(() =>
createSpecializedBackend({
- services: [
+ defaultServiceFactories: [
createServiceFactory({
service: coreServices.pluginMetadata,
deps: {},
diff --git a/packages/backend-app-api/src/wiring/createSpecializedBackend.ts b/packages/backend-app-api/src/wiring/createSpecializedBackend.ts
index 15e51f7c4c..f5c4d8b152 100644
--- a/packages/backend-app-api/src/wiring/createSpecializedBackend.ts
+++ b/packages/backend-app-api/src/wiring/createSpecializedBackend.ts
@@ -24,7 +24,7 @@ import { Backend, CreateSpecializedBackendOptions } from './types';
export function createSpecializedBackend(
options: CreateSpecializedBackendOptions,
): Backend {
- const services = options.services.map(sf =>
+ const services = options.defaultServiceFactories.map(sf =>
typeof sf === 'function' ? sf() : sf,
);
diff --git a/packages/backend-app-api/src/wiring/types.ts b/packages/backend-app-api/src/wiring/types.ts
index ffedb40324..552a69a792 100644
--- a/packages/backend-app-api/src/wiring/types.ts
+++ b/packages/backend-app-api/src/wiring/types.ts
@@ -25,7 +25,7 @@ import {
* @public
*/
export interface Backend {
- add(feature: BackendFeature): void;
+ add(feature: BackendFeature | (() => BackendFeature)): void;
start(): Promise;
stop(): Promise;
}
@@ -34,7 +34,7 @@ export interface Backend {
* @public
*/
export interface CreateSpecializedBackendOptions {
- services: ServiceFactoryOrFunction[];
+ defaultServiceFactories: ServiceFactoryOrFunction[];
}
export interface ServiceHolder {
diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md
index 2977d54c01..030223606f 100644
--- a/packages/backend-common/CHANGELOG.md
+++ b/packages/backend-common/CHANGELOG.md
@@ -1,5 +1,56 @@
# @backstage/backend-common
+## 0.19.2
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 443afcf7f567: To improve performance, `GerritUrlReader.readTree()` now uses Gitiles to fetch an archive instead of cloning the repository.
+ If `gitilesBaseUrl` is not configured, `readTree` still uses Git to clone the repository.
+
+ Added `stripFirstDirectory` option to `ReadTreeResponseFactory.fromTarArchive()`, allowing to disable stripping first directory
+ for `tar` archives.
+
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-app-api@0.5.0
+ - @backstage/config-loader@1.4.0
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/integration@1.6.0
+ - @backstage/integration-aws-node@0.1.5
+ - @backstage/backend-dev-utils@0.1.1
+ - @backstage/cli-common@0.1.12
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
+## 0.19.2-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-app-api@0.5.0-next.2
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/config-loader@1.4.0-next.1
+
+## 0.19.2-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/config-loader@1.4.0-next.1
+ - @backstage/backend-app-api@0.5.0-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/integration@1.5.1
+ - @backstage/integration-aws-node@0.1.5
+ - @backstage/backend-dev-utils@0.1.1
+ - @backstage/cli-common@0.1.12
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
## 0.19.2-next.0
### Patch Changes
diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md
index d40027cba9..f8e535c8a7 100644
--- a/packages/backend-common/api-report.md
+++ b/packages/backend-common/api-report.md
@@ -18,7 +18,6 @@ import { CacheService as CacheClient } from '@backstage/backend-plugin-api';
import { CacheServiceOptions as CacheClientOptions } from '@backstage/backend-plugin-api';
import { CacheServiceSetOptions as CacheClientSetOptions } from '@backstage/backend-plugin-api';
import { Config } from '@backstage/config';
-import { ConfigService } from '@backstage/backend-plugin-api';
import cors from 'cors';
import Docker from 'dockerode';
import { ErrorRequestHandler } from 'express';
@@ -51,6 +50,7 @@ import { ReadTreeResponseFile } from '@backstage/backend-plugin-api';
import { ReadUrlOptions } from '@backstage/backend-plugin-api';
import { ReadUrlResponse } from '@backstage/backend-plugin-api';
import { RequestHandler } from 'express';
+import { RootConfigService } from '@backstage/backend-plugin-api';
import { Router } from 'express';
import { SchedulerService } from '@backstage/backend-plugin-api';
import { SearchOptions } from '@backstage/backend-plugin-api';
@@ -525,7 +525,7 @@ export const legacyPlugin: (
TransformedEnv<
{
cache: CacheClient;
- config: ConfigService;
+ config: RootConfigService;
database: PluginDatabaseManager;
discovery: PluginEndpointDiscovery;
logger: LoggerService;
@@ -610,7 +610,9 @@ export interface ReadTreeResponseFactory {
): Promise;
// (undocumented)
fromTarArchive(
- options: ReadTreeResponseFactoryOptions,
+ options: ReadTreeResponseFactoryOptions & {
+ stripFirstDirectory?: boolean;
+ },
): Promise;
// (undocumented)
fromZipArchive(
diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts
index 3fd886874e..e5b193c543 100644
--- a/packages/backend-common/config.d.ts
+++ b/packages/backend-common/config.d.ts
@@ -77,17 +77,17 @@ export interface Config {
*/
connection:
| string
- | Partial<{
+ | {
/**
* Password that belongs to the client User
* @visibility secret
*/
- password: string;
+ password?: string;
/**
* Other connection settings
*/
[key: string]: unknown;
- }>;
+ };
/** Database name prefix override */
prefix?: string;
/**
diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json
index 6e561fa564..fd9b9cb0df 100644
--- a/packages/backend-common/package.json
+++ b/packages/backend-common/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-common",
"description": "Common functionality library for Backstage backends",
- "version": "0.19.2-next.0",
+ "version": "0.19.2",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
diff --git a/packages/backend-common/src/legacy.ts b/packages/backend-common/src/legacy.ts
index 408966f3bc..df1030e6b6 100644
--- a/packages/backend-common/src/legacy.ts
+++ b/packages/backend-common/src/legacy.ts
@@ -108,7 +108,7 @@ export function makeLegacyPlugin<
export const legacyPlugin = makeLegacyPlugin(
{
cache: coreServices.cache,
- config: coreServices.config,
+ config: coreServices.rootConfig,
database: coreServices.database,
discovery: coreServices.discovery,
logger: coreServices.logger,
diff --git a/packages/backend-common/src/reading/GerritUrlReader.test.ts b/packages/backend-common/src/reading/GerritUrlReader.test.ts
index 4e0b97fc58..9de9aac33d 100644
--- a/packages/backend-common/src/reading/GerritUrlReader.test.ts
+++ b/packages/backend-common/src/reading/GerritUrlReader.test.ts
@@ -37,14 +37,16 @@ const treeResponseFactory = DefaultReadTreeResponseFactory.create({
config: new ConfigReader({}),
});
+const cloneMock = jest.fn(() => Promise.resolve());
jest.mock('../scm', () => ({
Git: {
fromAuth: () => ({
- clone: jest.fn(() => Promise.resolve({})),
+ clone: cloneMock,
}),
},
}));
+// Gerrit processor without a gitilesBaseUrl configured
const gerritProcessor = new GerritUrlReader(
new GerritIntegration(
readGerritIntegrationConfig(
@@ -57,6 +59,21 @@ const gerritProcessor = new GerritUrlReader(
'/tmp',
);
+// Gerrit processor with a gitilesBaseUrl configured.
+// Use to test readTree with Gitiles archive download.
+const gerritProcessorWithGitiles = new GerritUrlReader(
+ new GerritIntegration(
+ readGerritIntegrationConfig(
+ new ConfigReader({
+ host: 'gerrit.com',
+ gitilesBaseUrl: 'https://gerrit.com/gitiles',
+ }),
+ ),
+ ),
+ { treeResponseFactory },
+ '/tmp',
+);
+
const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => {
return GerritUrlReader.factory({
config: new ConfigReader(config),
@@ -217,9 +234,17 @@ describe('GerritUrlReader', () => {
path.resolve(__dirname, '__fixtures__/gerrit/branch-info-response.txt'),
);
const treeUrl = 'https://gerrit.com/app/web/+/refs/heads/master/';
+ const treeUrlGitiles =
+ 'https://gerrit.com/gitiles/app/web/+/refs/heads/master/';
const etag = '52432507a70b677b5674b019c9a46b2e9f29d0a1';
- const mkdocsContent = 'great content';
+ const mkdocsContent = 'a repo fetched using git clone';
const mdContent = 'doc';
+ const repoArchiveBuffer = fs.readFileSync(
+ path.resolve(__dirname, '__fixtures__/gerrit/gerrit-master.tar.gz'),
+ );
+ const repoArchiveDocsBuffer = fs.readFileSync(
+ path.resolve(__dirname, '__fixtures__/gerrit/gerrit-master-docs.tar.gz'),
+ );
beforeEach(() => {
mockFs({
@@ -229,6 +254,35 @@ describe('GerritUrlReader', () => {
});
const spy = jest.spyOn(fs, 'mkdtemp');
spy.mockImplementation(() => '/tmp/gerrit-clone-123abc');
+
+ worker.use(
+ rest.get(
+ 'https://gerrit.com/gitiles/app/web/\\+archive/refs/heads/master.tar.gz',
+ (_, res, ctx) =>
+ res(
+ ctx.status(200),
+ ctx.set('Content-Type', 'application/x-gzip'),
+ ctx.set(
+ 'content-disposition',
+ 'attachment; filename=web-refs/heads/master.tar.gz',
+ ),
+ ctx.body(repoArchiveBuffer),
+ ),
+ ),
+ rest.get(
+ 'https://gerrit.com/gitiles/app/web/\\+archive/refs/heads/master/docs.tar.gz',
+ (_, res, ctx) =>
+ res(
+ ctx.status(200),
+ ctx.set('Content-Type', 'application/x-gzip'),
+ ctx.set(
+ 'content-disposition',
+ 'attachment; filename=web-refs/heads/master-docs.tar.gz',
+ ),
+ ctx.body(repoArchiveDocsBuffer),
+ ),
+ ),
+ );
});
afterEach(() => {
@@ -236,7 +290,32 @@ describe('GerritUrlReader', () => {
jest.clearAllMocks();
});
- it('reads the wanted files correctly.', async () => {
+ it('reads the wanted files correctly using gitiles.', async () => {
+ worker.use(
+ rest.get(branchAPIUrl, (_, res, ctx) => {
+ return res(ctx.status(200), ctx.body(branchAPIresponse));
+ }),
+ );
+
+ const response = await gerritProcessorWithGitiles.readTree(
+ treeUrlGitiles,
+ );
+
+ expect(response.etag).toBe(etag);
+
+ const files = await response.files();
+ expect(files.length).toBe(2);
+
+ const docsYaml = await files[0].content();
+ expect(docsYaml.toString()).toBe('# Test\n');
+
+ const mdFile = await files[1].content();
+ expect(mdFile.toString()).toBe('site_name: Test\n');
+
+ expect(cloneMock).not.toHaveBeenCalled();
+ });
+
+ it('reads the wanted files correctly using git clone.', async () => {
worker.use(
rest.get(branchAPIUrl, (_, res, ctx) => {
return res(ctx.status(200), ctx.body(branchAPIresponse));
@@ -255,6 +334,8 @@ describe('GerritUrlReader', () => {
const mdFile = await files[1].content();
expect(mdFile.toString()).toBe(mdContent);
+
+ expect(cloneMock).toHaveBeenCalled();
});
it('throws NotModifiedError for matching etags.', async () => {
@@ -291,7 +372,29 @@ describe('GerritUrlReader', () => {
await expect(gerritProcessor.readTree(treeUrl)).rejects.toThrow(Error);
});
- it('should returns wanted files with a subpath', async () => {
+ it('should returns wanted files with a subpath using gitiles', async () => {
+ worker.use(
+ rest.get(branchAPIUrl, (_, res, ctx) => {
+ return res(ctx.status(200), ctx.body(branchAPIresponse));
+ }),
+ );
+
+ const response = await gerritProcessorWithGitiles.readTree(
+ `${treeUrlGitiles}/docs`,
+ );
+
+ expect(response.etag).toBe(etag);
+
+ const files = await response.files();
+ expect(files.length).toBe(1);
+
+ const mdFile = await files[0].content();
+ expect(mdFile.toString()).toBe('# Test\n');
+
+ expect(cloneMock).not.toHaveBeenCalled();
+ });
+
+ it('should returns wanted files with a subpath using git clone', async () => {
worker.use(
rest.get(branchAPIUrl, (_, res, ctx) => {
return res(ctx.status(200), ctx.body(branchAPIresponse));
@@ -307,6 +410,8 @@ describe('GerritUrlReader', () => {
const mdFile = await files[0].content();
expect(mdFile.toString()).toBe(mdContent);
+
+ expect(cloneMock).toHaveBeenCalled();
});
});
});
diff --git a/packages/backend-common/src/reading/GerritUrlReader.ts b/packages/backend-common/src/reading/GerritUrlReader.ts
index 8ac5452a4c..f4a5fdb9f5 100644
--- a/packages/backend-common/src/reading/GerritUrlReader.ts
+++ b/packages/backend-common/src/reading/GerritUrlReader.ts
@@ -14,16 +14,17 @@
* limitations under the License.
*/
-import { Git } from '../scm';
import { NotFoundError, NotModifiedError } from '@backstage/errors';
import {
GerritIntegration,
- getGerritCloneRepoUrl,
+ ScmIntegrations,
+ buildGerritGitilesArchiveUrl,
getGerritBranchApiUrl,
+ getGerritCloneRepoUrl,
getGerritFileContentsApiUrl,
getGerritRequestOptions,
- parseGerritJsonResponse,
parseGerritGitilesUrl,
+ parseGerritJsonResponse,
} from '@backstage/integration';
import { Base64Decode } from 'base64-stream';
import concatStream from 'concat-stream';
@@ -31,20 +32,20 @@ import fs from 'fs-extra';
import fetch, { Response } from 'node-fetch';
import os from 'os';
import { join as joinPath } from 'path';
+import { Readable, pipeline as pipelineCb } from 'stream';
import tar from 'tar';
-import { pipeline as pipelineCb, Readable } from 'stream';
import { promisify } from 'util';
+import { Git } from '../scm';
import {
- ReaderFactory,
ReadTreeOptions,
ReadTreeResponse,
ReadTreeResponseFactory,
ReadUrlOptions,
ReadUrlResponse,
+ ReaderFactory,
SearchResponse,
UrlReader,
} from './types';
-import { ScmIntegrations } from '@backstage/integration';
const pipeline = promisify(pipelineCb);
@@ -59,6 +60,8 @@ const createTemporaryDirectory = async (workDir: string): Promise =>
* way we are depending on that there is a Gitiles installation somewhere
* that we can link to. It is perfectly possible to integrate Gerrit with
* Backstage without Gitiles since all API calls goes directly to Gerrit.
+ * However if Gitiles is configured, readTree will use it to fetch
+ * an archive instead of cloning the repository.
*
* The "host" variable in the config is the Gerrit host. The address where
* Gitiles is installed may be on the same host but it could be on a
@@ -125,6 +128,7 @@ export class GerritUrlReader implements UrlReader {
} catch (e) {
throw new Error(`Unable to read gerrit file ${url}, ${e}`);
}
+
if (response.ok) {
let responseBody: string;
return {
@@ -152,7 +156,6 @@ export class GerritUrlReader implements UrlReader {
url: string,
options?: ReadTreeOptions,
): Promise {
- const { filePath } = parseGerritGitilesUrl(this.integration.config, url);
const apiUrl = getGerritBranchApiUrl(this.integration.config, url);
let response: Response;
try {
@@ -180,6 +183,30 @@ export class GerritUrlReader implements UrlReader {
throw new NotModifiedError();
}
+ if (
+ this.integration.config.gitilesBaseUrl !== this.integration.config.baseUrl
+ ) {
+ return this.readTreeFromGitiles(url, branchInfo.revision, options);
+ }
+ return this.readTreeFromGitClone(url, branchInfo.revision, options);
+ }
+
+ async search(): Promise {
+ throw new Error('GerritReader does not implement search');
+ }
+
+ toString() {
+ const { host, password } = this.integration.config;
+ return `gerrit{host=${host},authed=${Boolean(password)}}`;
+ }
+
+ private async readTreeFromGitClone(
+ url: string,
+ revision: string,
+ options?: ReadTreeOptions,
+ ) {
+ const { filePath } = parseGerritGitilesUrl(this.integration.config, url);
+
const git = Git.fromAuth({
username: this.integration.config.username,
password: this.integration.config.password,
@@ -192,7 +219,7 @@ export class GerritUrlReader implements UrlReader {
await git.clone({
url: cloneUrl,
dir: joinPath(tempDir, 'repo'),
- ref: branchInfo.revision,
+ ref: revision,
depth: 1,
});
@@ -206,7 +233,7 @@ export class GerritUrlReader implements UrlReader {
return await this.deps.treeResponseFactory.fromTarArchive({
stream: tarArchive,
subpath: filePath === '/' ? undefined : filePath,
- etag: branchInfo.revision,
+ etag: revision,
filter: options?.filter,
});
} catch (error) {
@@ -216,12 +243,47 @@ export class GerritUrlReader implements UrlReader {
}
}
- async search(): Promise {
- throw new Error('GerritReader does not implement search');
- }
+ private async readTreeFromGitiles(
+ url: string,
+ revision: string,
+ options?: ReadTreeOptions,
+ ) {
+ const { branch, filePath, project } = parseGerritGitilesUrl(
+ this.integration.config,
+ url,
+ );
+ const archiveUrl = buildGerritGitilesArchiveUrl(
+ this.integration.config,
+ project,
+ branch,
+ filePath,
+ );
+ const archiveResponse = await fetch(archiveUrl, {
+ ...getGerritRequestOptions(this.integration.config),
+ // TODO(freben): The signal cast is there because pre-3.x versions of
+ // node-fetch have a very slightly deviating AbortSignal type signature.
+ // The difference does not affect us in practice however. The cast can
+ // be removed after we support ESM for CLI dependencies and migrate to
+ // version 3 of node-fetch.
+ // https://github.com/backstage/backstage/issues/8242
+ signal: options?.signal as any,
+ });
- toString() {
- const { host, password } = this.integration.config;
- return `gerrit{host=${host},authed=${Boolean(password)}}`;
+ if (archiveResponse.status === 404) {
+ throw new NotFoundError(`Not found: ${archiveUrl}`);
+ }
+
+ if (!archiveResponse.ok) {
+ throw new Error(
+ `${url} could not be read as ${archiveUrl}, ${archiveResponse.status} ${archiveResponse.statusText}`,
+ );
+ }
+
+ return await this.deps.treeResponseFactory.fromTarArchive({
+ stream: archiveResponse.body as unknown as Readable,
+ etag: revision,
+ filter: options?.filter,
+ stripFirstDirectory: false,
+ });
}
}
diff --git a/packages/backend-common/src/reading/__fixtures__/gerrit/gerrit-master-docs.tar.gz b/packages/backend-common/src/reading/__fixtures__/gerrit/gerrit-master-docs.tar.gz
new file mode 100644
index 0000000000..425cf0876b
Binary files /dev/null and b/packages/backend-common/src/reading/__fixtures__/gerrit/gerrit-master-docs.tar.gz differ
diff --git a/packages/backend-common/src/reading/__fixtures__/gerrit/gerrit-master.tar.gz b/packages/backend-common/src/reading/__fixtures__/gerrit/gerrit-master.tar.gz
new file mode 100644
index 0000000000..8a27502734
Binary files /dev/null and b/packages/backend-common/src/reading/__fixtures__/gerrit/gerrit-master.tar.gz differ
diff --git a/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts b/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts
index 11a5ef168a..90c58c0efa 100644
--- a/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts
+++ b/packages/backend-common/src/reading/tree/ReadTreeResponseFactory.ts
@@ -37,7 +37,9 @@ export class DefaultReadTreeResponseFactory implements ReadTreeResponseFactory {
constructor(private readonly workDir: string) {}
async fromTarArchive(
- options: ReadTreeResponseFactoryOptions,
+ options: ReadTreeResponseFactoryOptions & {
+ stripFirstDirectory?: boolean;
+ },
): Promise {
return new TarArchiveResponse(
options.stream,
@@ -45,6 +47,7 @@ export class DefaultReadTreeResponseFactory implements ReadTreeResponseFactory {
this.workDir,
options.etag,
options.filter,
+ options.stripFirstDirectory ?? true,
);
}
diff --git a/packages/backend-common/src/reading/tree/TarArchiveResponse.ts b/packages/backend-common/src/reading/tree/TarArchiveResponse.ts
index e1529c196a..294ec22609 100644
--- a/packages/backend-common/src/reading/tree/TarArchiveResponse.ts
+++ b/packages/backend-common/src/reading/tree/TarArchiveResponse.ts
@@ -44,6 +44,7 @@ export class TarArchiveResponse implements ReadTreeResponse {
private readonly workDir: string,
public readonly etag: string,
private readonly filter?: (path: string, info: { size: number }) => boolean,
+ private readonly stripFirstDirectory: boolean = true,
) {
if (subPath) {
if (!subPath.endsWith('/')) {
@@ -81,7 +82,9 @@ export class TarArchiveResponse implements ReadTreeResponse {
// File path relative to the root extracted directory. Will remove the
// top level dir name from the path since its name is hard to predetermine.
- const relativePath = stripFirstDirectoryFromPath(entry.path);
+ const relativePath = this.stripFirstDirectory
+ ? stripFirstDirectoryFromPath(entry.path)
+ : entry.path;
if (this.subPath) {
if (!relativePath.startsWith(this.subPath)) {
@@ -148,7 +151,10 @@ export class TarArchiveResponse implements ReadTreeResponse {
// Equivalent of tar --strip-components=N
// When no subPath is given, remove just 1 top level directory
- const strip = this.subPath ? this.subPath.split('/').length : 1;
+ let strip = this.subPath ? this.subPath.split('/').length : 1;
+ if (!this.stripFirstDirectory) {
+ strip--;
+ }
let filterError: Error | undefined = undefined;
await pipeline(
@@ -164,7 +170,9 @@ export class TarArchiveResponse implements ReadTreeResponse {
// File path relative to the root extracted directory. Will remove the
// top level dir name from the path since its name is hard to predetermine.
- const relativePath = stripFirstDirectoryFromPath(path);
+ const relativePath = this.stripFirstDirectory
+ ? stripFirstDirectoryFromPath(path)
+ : path;
if (this.subPath && !relativePath.startsWith(this.subPath)) {
return false;
}
diff --git a/packages/backend-common/src/reading/types.ts b/packages/backend-common/src/reading/types.ts
index 7f4d407fd0..90a3c6287e 100644
--- a/packages/backend-common/src/reading/types.ts
+++ b/packages/backend-common/src/reading/types.ts
@@ -116,7 +116,13 @@ export type FromReadableArrayOptions = Array<{
*/
export interface ReadTreeResponseFactory {
fromTarArchive(
- options: ReadTreeResponseFactoryOptions,
+ options: ReadTreeResponseFactoryOptions & {
+ /**
+ * Strip the first parent directory of a tar archive.
+ * Defaults to true.
+ */
+ stripFirstDirectory?: boolean;
+ },
): Promise;
fromZipArchive(
options: ReadTreeResponseFactoryOptions,
diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md
index 1ff580965f..af86313d11 100644
--- a/packages/backend-defaults/CHANGELOG.md
+++ b/packages/backend-defaults/CHANGELOG.md
@@ -1,5 +1,54 @@
# @backstage/backend-defaults
+## 0.2.0
+
+### Minor Changes
+
+- d008aefef808: **BREAKING**: Removing shared environments concept from the new experimental backend system.
+- a6d7983f349c: **BREAKING**: Removed the `services` option from `createBackend`. Service factories are now `BackendFeature`s and should be installed with `backend.add(...)` instead. The following should be migrated:
+
+ ```ts
+ const backend = createBackend({ services: [myCustomServiceFactory] });
+ ```
+
+ To instead pass the service factory via `backend.add(...)`:
+
+ ```ts
+ const backend = createBackend();
+ backend.add(customRootLoggerServiceFactory);
+ ```
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-app-api@0.5.0
+ - @backstage/backend-plugin-api@0.6.0
+
+## 0.2.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-app-api@0.5.0-next.2
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-common@0.19.2-next.2
+
+## 0.2.0-next.1
+
+### Minor Changes
+
+- d008aefef808: **BREAKING**: Removing shared environments concept from the new experimental backend system.
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/backend-app-api@0.5.0-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+
## 0.1.13-next.0
### Patch Changes
diff --git a/packages/backend-defaults/api-report.md b/packages/backend-defaults/api-report.md
index 07218f610e..ab9cc5775c 100644
--- a/packages/backend-defaults/api-report.md
+++ b/packages/backend-defaults/api-report.md
@@ -4,17 +4,7 @@
```ts
import { Backend } from '@backstage/backend-app-api';
-import { ServiceFactoryOrFunction } from '@backstage/backend-plugin-api';
-import { SharedBackendEnvironment } from '@backstage/backend-plugin-api';
// @public (undocumented)
-export function createBackend(options?: CreateBackendOptions): Backend;
-
-// @public (undocumented)
-export interface CreateBackendOptions {
- // (undocumented)
- env?: SharedBackendEnvironment;
- // (undocumented)
- services?: ServiceFactoryOrFunction[];
-}
+export function createBackend(): Backend;
```
diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json
index 318b00c7f2..48464ac81e 100644
--- a/packages/backend-defaults/package.json
+++ b/packages/backend-defaults/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-defaults",
"description": "Backend defaults used by Backstage backend apps",
- "version": "0.1.13-next.0",
+ "version": "0.2.0",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
diff --git a/packages/backend-defaults/src/CreateBackend.test.ts b/packages/backend-defaults/src/CreateBackend.test.ts
index 946de90b2b..0b36b57069 100644
--- a/packages/backend-defaults/src/CreateBackend.test.ts
+++ b/packages/backend-defaults/src/CreateBackend.test.ts
@@ -16,160 +16,69 @@
import {
coreServices,
- createBackendPlugin,
createServiceFactory,
- createServiceRef,
- createSharedEnvironment,
} from '@backstage/backend-plugin-api';
-import { mockServices } from '@backstage/backend-test-utils';
import { createBackend } from './CreateBackend';
-const fooServiceRef = createServiceRef({ id: 'foo', scope: 'root' });
-const barServiceRef = createServiceRef({ id: 'bar', scope: 'root' });
-
describe('createBackend', () => {
it('should not throw when overriding a default service implementation', () => {
- expect(() =>
- createBackend({
- services: [
- createServiceFactory({
- service: coreServices.rootLifecycle,
- deps: {},
- factory: async () => ({
- addStartupHook: () => {},
- addShutdownHook: () => {},
- }),
+ const backend = createBackend();
+
+ expect(() => {
+ backend.add(
+ createServiceFactory({
+ service: coreServices.rootLifecycle,
+ deps: {},
+ factory: async () => ({
+ addStartupHook: () => {},
+ addShutdownHook: () => {},
}),
- ],
- }),
- ).not.toThrow();
+ }),
+ );
+ }).not.toThrow();
});
it('should throw on duplicate service implementations', () => {
- expect(() =>
- createBackend({
- services: [
- createServiceFactory({
- service: coreServices.rootLifecycle,
- deps: {},
- factory: async () => ({
- addStartupHook: () => {},
- addShutdownHook: () => {},
- }),
- }),
- createServiceFactory({
- service: coreServices.rootLifecycle,
- deps: {},
- factory: async () => ({
- addStartupHook: () => {},
- addShutdownHook: () => {},
- }),
- }),
- ],
+ const backend = createBackend();
+
+ backend.add(
+ createServiceFactory({
+ service: coreServices.rootLifecycle,
+ deps: {},
+ factory: async () => ({
+ addStartupHook: () => {},
+ addShutdownHook: () => {},
+ }),
}),
- ).toThrow(
+ );
+
+ expect(() => {
+ backend.add(
+ createServiceFactory({
+ service: coreServices.rootLifecycle,
+ deps: {},
+ factory: async () => ({
+ addStartupHook: () => {},
+ addShutdownHook: () => {},
+ }),
+ }),
+ );
+ }).toThrow(
'Duplicate service implementations provided for core.rootLifecycle',
);
});
it('should throw when providing a plugin metadata service implementation', () => {
+ const backend = createBackend();
+
expect(() =>
- createBackend({
- services: [
- createServiceFactory({
- service: coreServices.pluginMetadata,
- deps: {},
- factory: async () => ({ getId: () => 'test' }),
- }),
- ],
- }),
+ backend.add(
+ createServiceFactory({
+ service: coreServices.pluginMetadata,
+ deps: {},
+ factory: async () => ({ getId: () => 'test' }),
+ }),
+ ),
).toThrow('The core.pluginMetadata service cannot be overridden');
});
-
- it('should throw if an unsupported InternalSharedEnvironment version is passed in', () => {
- expect(() =>
- createBackend({
- env: {} as any,
- }),
- ).toThrow(
- "Shared environment version 'undefined' is invalid or not supported",
- );
- expect(() =>
- createBackend({
- env: { version: {} } as any,
- }),
- ).toThrow(
- "Shared environment version '[object Object]' is invalid or not supported",
- );
- expect(() =>
- createBackend({
- env: { version: 'v2' } as any,
- }),
- ).toThrow("Shared environment version 'v2' is invalid or not supported");
- });
-
- it('should prioritize services correctly', async () => {
- const backend = createBackend({
- env: createSharedEnvironment({
- services: [
- createServiceFactory({
- service: coreServices.rootHttpRouter,
- deps: {},
- async factory() {
- return {
- use() {},
- };
- },
- }),
- mockServices.config.factory({
- data: { root: 'root-env' },
- }),
- createServiceFactory({
- service: fooServiceRef,
- deps: {},
- async factory() {
- return 'foo-env';
- },
- }),
- createServiceFactory({
- service: barServiceRef,
- deps: {},
- async factory() {
- return 'bar-env';
- },
- }),
- ],
- })(),
- services: [
- createServiceFactory({
- service: fooServiceRef,
- deps: {},
- factory: async () => 'foo-backend',
- }),
- ],
- });
-
- expect.assertions(3);
- backend.add(
- createBackendPlugin({
- pluginId: 'test',
- register(reg) {
- reg.registerInit({
- deps: {
- config: coreServices.config,
- foo: fooServiceRef,
- bar: barServiceRef,
- },
- async init({ config, foo, bar }) {
- expect(config.get('root')).toBe('root-env');
- expect(foo).toBe('foo-backend');
- expect(bar).toBe('bar-env');
- },
- });
- },
- })(),
- );
-
- await backend.start();
- });
});
diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts
index 480f365216..c07d1cc38a 100644
--- a/packages/backend-defaults/src/CreateBackend.ts
+++ b/packages/backend-defaults/src/CreateBackend.ts
@@ -17,7 +17,7 @@
import {
Backend,
cacheServiceFactory,
- configServiceFactory,
+ rootConfigServiceFactory,
createSpecializedBackend,
databaseServiceFactory,
discoveryServiceFactory,
@@ -33,19 +33,10 @@ import {
urlReaderServiceFactory,
identityServiceFactory,
} from '@backstage/backend-app-api';
-import {
- ServiceFactory,
- ServiceFactoryOrFunction,
- SharedBackendEnvironment,
-} from '@backstage/backend-plugin-api';
-
-// Internal import of the type to avoid needing to export this.
-// eslint-disable-next-line @backstage/no-forbidden-package-imports
-import type { InternalSharedBackendEnvironment } from '@backstage/backend-plugin-api/src/wiring/createSharedEnvironment';
export const defaultServiceFactories = [
cacheServiceFactory(),
- configServiceFactory(),
+ rootConfigServiceFactory(),
databaseServiceFactory(),
discoveryServiceFactory(),
httpRouterServiceFactory(),
@@ -64,44 +55,6 @@ export const defaultServiceFactories = [
/**
* @public
*/
-export interface CreateBackendOptions {
- env?: SharedBackendEnvironment;
- services?: ServiceFactoryOrFunction[];
-}
-
-/**
- * @public
- */
-export function createBackend(options?: CreateBackendOptions): Backend {
- const services = new Array();
-
- // Highest priority: Services passed directly to createBackend
- const providedServices = (options?.services ?? []).map(sf =>
- typeof sf === 'function' ? sf() : sf,
- );
- services.push(...providedServices);
-
- // Middle priority: Services from the shared environment
- if (options?.env) {
- const env = options.env as unknown as InternalSharedBackendEnvironment;
- if (env.version !== 'v1') {
- throw new Error(
- `Shared environment version '${env.version}' is invalid or not supported`,
- );
- }
-
- const environmentServices =
- env.services?.filter(
- sf => !services.some(({ service }) => sf.service.id === service.id),
- ) ?? [];
- services.push(...environmentServices);
- }
-
- // Lowest priority: Default services that are not already provided by environment or directly to createBackend
- const defaultServices = defaultServiceFactories.filter(
- sf => !services.some(({ service }) => service.id === sf.service.id),
- );
- services.push(...defaultServices);
-
- return createSpecializedBackend({ services });
+export function createBackend(): Backend {
+ return createSpecializedBackend({ defaultServiceFactories });
}
diff --git a/packages/backend-defaults/src/index.ts b/packages/backend-defaults/src/index.ts
index 833878744e..d3e36cf0fb 100644
--- a/packages/backend-defaults/src/index.ts
+++ b/packages/backend-defaults/src/index.ts
@@ -25,5 +25,4 @@
// TODO(Rugvip): Remove this once backend-common is no longer used by backend-app-api
import '@backstage/backend-common';
-export type { CreateBackendOptions } from './CreateBackend';
export { createBackend } from './CreateBackend';
diff --git a/packages/backend-next/.snyk b/packages/backend-next/.snyk
new file mode 100644
index 0000000000..ea5abd653d
--- /dev/null
+++ b/packages/backend-next/.snyk
@@ -0,0 +1,10 @@
+# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
+version: v1.22.1
+# ignores vulnerabilities until expiry date; change duration by modifying expiry date
+ignore:
+ SNYK-JS-ISOLATEDVM-3037320:
+ - '*':
+ reason: We do not pass any V8 cache data, and are therefore unaffected by this vulnerability
+ expires: 2033-07-02T16:55:57.077Z
+ created: 2023-07-02T16:55:57.077Z
+patch: {}
diff --git a/packages/backend-next/CHANGELOG.md b/packages/backend-next/CHANGELOG.md
index 9a9be3b3e2..2c0fefb9f3 100644
--- a/packages/backend-next/CHANGELOG.md
+++ b/packages/backend-next/CHANGELOG.md
@@ -1,5 +1,101 @@
# example-backend-next
+## 0.0.14
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-search-backend-module-techdocs@0.1.4
+ - @backstage/plugin-search-backend-module-catalog@0.1.4
+ - @backstage/plugin-search-backend-module-explore@0.1.4
+ - @backstage/plugin-azure-devops-backend@0.3.27
+ - @backstage/plugin-kubernetes-backend@0.11.3
+ - @backstage/plugin-lighthouse-backend@0.2.4
+ - @backstage/plugin-permission-backend@0.5.23
+ - @backstage/plugin-scaffolder-backend@1.16.0
+ - @backstage/backend-defaults@0.2.0
+ - @backstage/plugin-devtools-backend@0.1.3
+ - @backstage/plugin-techdocs-backend@1.6.5
+ - @backstage/plugin-catalog-backend@1.12.0
+ - @backstage/plugin-badges-backend@0.2.3
+ - @backstage/plugin-search-backend@1.4.0
+ - @backstage/plugin-proxy-backend@0.3.0
+ - @backstage/plugin-todo-backend@0.2.0
+ - @backstage/plugin-app-backend@0.3.48
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0
+ - @backstage/plugin-entity-feedback-backend@0.1.6
+ - @backstage/plugin-search-backend-node@1.2.4
+ - @backstage/plugin-linguist-backend@0.4.0
+ - @backstage/plugin-auth-node@0.2.17
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/plugin-adr-backend@0.3.6
+ - @backstage/plugin-permission-node@0.7.11
+ - @backstage/plugin-permission-common@0.7.7
+
+## 0.0.14-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-search-backend-module-techdocs@0.1.4-next.2
+ - @backstage/plugin-search-backend-module-catalog@0.1.4-next.2
+ - @backstage/plugin-search-backend-module-explore@0.1.4-next.2
+ - @backstage/plugin-scaffolder-backend@1.15.2-next.2
+ - @backstage/plugin-catalog-backend@1.12.0-next.2
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/plugin-proxy-backend@0.3.0-next.2
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/plugin-app-backend@0.3.48-next.2
+ - @backstage/plugin-linguist-backend@0.4.0-next.2
+ - @backstage/plugin-techdocs-backend@1.6.5-next.2
+ - @backstage/backend-defaults@0.2.0-next.2
+ - @backstage/plugin-adr-backend@0.3.6-next.2
+ - @backstage/plugin-azure-devops-backend@0.3.27-next.2
+ - @backstage/plugin-badges-backend@0.2.3-next.2
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0-next.2
+ - @backstage/plugin-devtools-backend@0.1.3-next.2
+ - @backstage/plugin-entity-feedback-backend@0.1.6-next.2
+ - @backstage/plugin-kubernetes-backend@0.11.3-next.2
+ - @backstage/plugin-lighthouse-backend@0.2.4-next.2
+ - @backstage/plugin-permission-backend@0.5.23-next.2
+ - @backstage/plugin-permission-node@0.7.11-next.2
+ - @backstage/plugin-search-backend@1.4.0-next.2
+ - @backstage/plugin-search-backend-node@1.2.4-next.2
+ - @backstage/plugin-todo-backend@0.2.0-next.2
+ - @backstage/plugin-auth-node@0.2.17-next.2
+
+## 0.0.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-search-backend-module-techdocs@0.1.4-next.1
+ - @backstage/plugin-search-backend-module-catalog@0.1.4-next.1
+ - @backstage/plugin-search-backend-module-explore@0.1.4-next.1
+ - @backstage/plugin-azure-devops-backend@0.3.27-next.1
+ - @backstage/plugin-kubernetes-backend@0.11.3-next.1
+ - @backstage/plugin-lighthouse-backend@0.2.4-next.1
+ - @backstage/plugin-permission-backend@0.5.23-next.1
+ - @backstage/plugin-scaffolder-backend@1.15.2-next.1
+ - @backstage/backend-defaults@0.2.0-next.1
+ - @backstage/plugin-devtools-backend@0.1.3-next.1
+ - @backstage/plugin-techdocs-backend@1.6.5-next.1
+ - @backstage/plugin-catalog-backend@1.12.0-next.1
+ - @backstage/plugin-badges-backend@0.2.3-next.1
+ - @backstage/plugin-search-backend@1.4.0-next.1
+ - @backstage/plugin-todo-backend@0.2.0-next.1
+ - @backstage/plugin-app-backend@0.3.48-next.1
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0-next.1
+ - @backstage/plugin-entity-feedback-backend@0.1.6-next.1
+ - @backstage/plugin-search-backend-node@1.2.4-next.1
+ - @backstage/plugin-linguist-backend@0.3.2-next.1
+ - @backstage/plugin-auth-node@0.2.17-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/plugin-adr-backend@0.3.6-next.1
+ - @backstage/plugin-permission-node@0.7.11-next.1
+ - @backstage/plugin-permission-common@0.7.7
+
## 0.0.14-next.0
### Patch Changes
diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json
index 3043a96ebd..c2b923ed26 100644
--- a/packages/backend-next/package.json
+++ b/packages/backend-next/package.json
@@ -1,6 +1,6 @@
{
"name": "example-backend-next",
- "version": "0.0.14-next.0",
+ "version": "0.0.14",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -26,6 +26,7 @@
},
"dependencies": {
"@backstage/backend-defaults": "workspace:^",
+ "@backstage/backend-plugin-api": "workspace:^",
"@backstage/backend-tasks": "workspace:^",
"@backstage/plugin-adr-backend": "workspace:^",
"@backstage/plugin-app-backend": "workspace:^",
@@ -42,6 +43,7 @@
"@backstage/plugin-permission-backend": "workspace:^",
"@backstage/plugin-permission-common": "workspace:^",
"@backstage/plugin-permission-node": "workspace:^",
+ "@backstage/plugin-proxy-backend": "workspace:^",
"@backstage/plugin-scaffolder-backend": "workspace:^",
"@backstage/plugin-search-backend": "workspace:^",
"@backstage/plugin-search-backend-module-catalog": "workspace:^",
diff --git a/packages/backend-next/src/index.ts b/packages/backend-next/src/index.ts
index fa7561e211..f3b1ea06f8 100644
--- a/packages/backend-next/src/index.ts
+++ b/packages/backend-next/src/index.ts
@@ -36,13 +36,13 @@ import { badgesPlugin } from '@backstage/plugin-badges-backend';
import { azureDevOpsPlugin } from '@backstage/plugin-azure-devops-backend';
import { linguistPlugin } from '@backstage/plugin-linguist-backend';
import { devtoolsPlugin } from '@backstage/plugin-devtools-backend';
-import { TaskScheduleDefinition } from '@backstage/backend-tasks';
import { adrPlugin } from '@backstage/plugin-adr-backend';
import { lighthousePlugin } from '@backstage/plugin-lighthouse-backend';
+import { proxyPlugin } from '@backstage/plugin-proxy-backend';
const backend = createBackend();
-backend.add(appPlugin({ appPackageName: 'example-app' }));
+backend.add(appPlugin());
// Badges
backend.add(badgesPlugin());
@@ -57,20 +57,7 @@ backend.add(devtoolsPlugin());
backend.add(entityFeedbackPlugin());
// Linguist
-const linguistSchedule: TaskScheduleDefinition = {
- frequency: { minutes: 2 },
- timeout: { minutes: 15 },
- initialDelay: { seconds: 15 },
-};
-
-backend.add(
- linguistPlugin({
- schedule: linguistSchedule,
- age: { days: 30 },
- batchSize: 2,
- useSourceLocation: false,
- }),
-);
+backend.add(linguistPlugin());
// Todo
backend.add(todoPlugin());
@@ -98,6 +85,9 @@ backend.add(kubernetesPlugin());
// Lighthouse
backend.add(lighthousePlugin());
+// Proxy
+backend.add(proxyPlugin());
+
// Permissions
backend.add(permissionPlugin());
backend.add(permissionModuleAllowAllPolicy());
diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md
index bd1d7f380a..fc3e52fa92 100644
--- a/packages/backend-openapi-utils/CHANGELOG.md
+++ b/packages/backend-openapi-utils/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/backend-openapi-utils
+## 0.0.3
+
+### Patch Changes
+
+- ebeb77586975: Add a new `createRouter` method for generating an `express` router that validates against your spec. Also fixes a bug with the query parameters type resolution.
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/errors@1.2.1
+
+## 0.0.3-next.1
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/errors@1.2.1
+
## 0.0.3-next.0
### Patch Changes
diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json
index 85a1677adb..102d8c36d6 100644
--- a/packages/backend-openapi-utils/package.json
+++ b/packages/backend-openapi-utils/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-openapi-utils",
"description": "OpenAPI typescript support.",
- "version": "0.0.3-next.0",
+ "version": "0.0.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -11,6 +11,12 @@
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "packages/backend-openapi-utils"
+ },
"backstage": {
"role": "node-library"
},
diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md
index a77cdb71eb..9ea8b95c03 100644
--- a/packages/backend-plugin-api/CHANGELOG.md
+++ b/packages/backend-plugin-api/CHANGELOG.md
@@ -1,5 +1,63 @@
# @backstage/backend-plugin-api
+## 0.6.0
+
+### Minor Changes
+
+- c49785f00cab: **BREAKING**: It is no longer possible to declare options as being required with `createServiceFactory`.
+- 629cbd194a87: **BREAKING**: Renamed `coreServices.config` to `coreServices.rootConfig`.
+- 51987dbdaf87: **BREAKING**: Removed the ability to define options for plugins and modules. Existing options should be migrated to instead use either static configuration or extension points.
+- d008aefef808: **BREAKING**: Removing shared environments concept from the new experimental backend system.
+
+### Patch Changes
+
+- c7aa4ff1793c: Allow modules to register extension points.
+- cc9256a33bcc: Added new experimental `featureDiscoveryServiceRef`, available as an `/alpha` export.
+- a6d7983f349c: **BREAKING**: Removed the `services` option from `createBackend`. Service factories are now `BackendFeature`s and should be installed with `backend.add(...)` instead. The following should be migrated:
+
+ ```ts
+ const backend = createBackend({ services: [myCustomServiceFactory] });
+ ```
+
+ To instead pass the service factory via `backend.add(...)`:
+
+ ```ts
+ const backend = createBackend();
+ backend.add(customRootLoggerServiceFactory);
+ ```
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.2.17
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/config@1.0.8
+ - @backstage/types@1.1.0
+ - @backstage/plugin-permission-common@0.7.7
+
+## 0.6.0-next.2
+
+### Patch Changes
+
+- cc9256a33bcc: Added new experimental `featureDiscoveryServiceRef`, available as an `/alpha` export.
+- Updated dependencies
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/plugin-auth-node@0.2.17-next.2
+
+## 0.6.0-next.1
+
+### Minor Changes
+
+- 629cbd194a87: **BREAKING**: Renamed `coreServices.config` to `coreServices.rootConfig`.
+- d008aefef808: **BREAKING**: Removing shared environments concept from the new experimental backend system.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.2.17-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/config@1.0.8
+ - @backstage/types@1.1.0
+ - @backstage/plugin-permission-common@0.7.7
+
## 0.5.5-next.0
### Patch Changes
diff --git a/packages/backend-plugin-api/alpha-api-report.md b/packages/backend-plugin-api/alpha-api-report.md
new file mode 100644
index 0000000000..81b378a671
--- /dev/null
+++ b/packages/backend-plugin-api/alpha-api-report.md
@@ -0,0 +1,24 @@
+## API Report File for "@backstage/backend-plugin-api"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { BackendFeature } from '@backstage/backend-plugin-api';
+import { ServiceRef } from '@backstage/backend-plugin-api';
+
+// @alpha (undocumented)
+export interface FeatureDiscoveryService {
+ // (undocumented)
+ getBackendFeatures(): Promise<{
+ features: Array;
+ }>;
+}
+
+// @alpha
+export const featureDiscoveryServiceRef: ServiceRef<
+ FeatureDiscoveryService,
+ 'root'
+>;
+
+// (No @packageDocumentation comment for this package)
+```
diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md
index b3cbef993c..17cc521499 100644
--- a/packages/backend-plugin-api/api-report.md
+++ b/packages/backend-plugin-api/api-report.md
@@ -31,6 +31,11 @@ export interface BackendModuleConfig {
// @public
export interface BackendModuleRegistrationPoints {
+ // (undocumented)
+ registerExtensionPoint(
+ ref: ExtensionPoint,
+ impl: TExtensionPoint,
+ ): void;
// (undocumented)
registerInit<
Deps extends {
@@ -93,13 +98,10 @@ export type CacheServiceSetOptions = {
ttl?: number;
};
-// @public (undocumented)
-export interface ConfigService extends Config {}
-
// @public
export namespace coreServices {
const cache: ServiceRef;
- const config: ServiceRef;
+ const rootConfig: ServiceRef;
const database: ServiceRef;
const discovery: ServiceRef;
const httpRouter: ServiceRef;
@@ -117,14 +119,14 @@ export namespace coreServices {
}
// @public
-export function createBackendModule(
- config: BackendModuleConfig | ((...params: TOptions) => BackendModuleConfig),
-): (...params: TOptions) => BackendFeature;
+export function createBackendModule(
+ config: BackendModuleConfig,
+): () => BackendFeature;
// @public
-export function createBackendPlugin(
- config: BackendPluginConfig | ((...params: TOptions) => BackendPluginConfig),
-): (...params: TOptions) => BackendFeature;
+export function createBackendPlugin(
+ config: BackendPluginConfig,
+): () => BackendFeature;
// @public
export function createExtensionPoint(
@@ -155,18 +157,6 @@ export function createServiceFactory<
config: (options?: TOpts) => RootServiceFactoryConfig,
): (options?: TOpts) => ServiceFactory;
-// @public
-export function createServiceFactory<
- TService,
- TImpl extends TService,
- TDeps extends {
- [name in string]: ServiceRef;
- },
- TOpts extends object | undefined = undefined,
->(
- config: (options: TOpts) => RootServiceFactoryConfig,
-): (options: TOpts) => ServiceFactory;
-
// @public
export function createServiceFactory<
TService,
@@ -195,23 +185,6 @@ export function createServiceFactory<
) => PluginServiceFactoryConfig,
): (options?: TOpts) => ServiceFactory;
-// @public
-export function createServiceFactory<
- TService,
- TImpl extends TService,
- TDeps extends {
- [name in string]: ServiceRef;
- },
- TContext = undefined,
- TOpts extends object | undefined = undefined,
->(
- config:
- | PluginServiceFactoryConfig
- | ((
- options: TOpts,
- ) => PluginServiceFactoryConfig),
-): (options: TOpts) => ServiceFactory;
-
// @public
export function createServiceRef(
config: ServiceRefConfig,
@@ -222,15 +195,6 @@ export function createServiceRef(
config: ServiceRefConfig,
): ServiceRef;
-// @public
-export function createSharedEnvironment<
- TOptions extends [options?: object] = [],
->(
- config:
- | SharedBackendEnvironmentConfig
- | ((...params: TOptions) => SharedBackendEnvironmentConfig),
-): (...options: TOptions) => SharedBackendEnvironment;
-
// @public
export interface DatabaseService {
getClient(): Promise;
@@ -389,6 +353,9 @@ export type ReadUrlResponse = {
lastModifiedAt?: Date;
};
+// @public (undocumented)
+export interface RootConfigService extends Config {}
+
// @public (undocumented)
export interface RootHttpRouterService {
use(path: string, handler: Handler): void;
@@ -442,9 +409,7 @@ export type SearchResponseFile = {
export interface ServiceFactory<
TService = unknown,
TScope extends 'plugin' | 'root' = 'plugin' | 'root',
-> {
- // (undocumented)
- $$type: '@backstage/ServiceFactory';
+> extends BackendFeature {
// (undocumented)
service: ServiceRef;
}
@@ -476,18 +441,6 @@ export interface ServiceRefConfig {
scope?: TScope;
}
-// @public
-export interface SharedBackendEnvironment {
- // (undocumented)
- $$type: '@backstage/SharedBackendEnvironment';
-}
-
-// @public
-export interface SharedBackendEnvironmentConfig {
- // (undocumented)
- services?: ServiceFactoryOrFunction[];
-}
-
// @public
export interface TokenManagerService {
authenticate(token: string): Promise;
diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json
index 91936a4c16..44a9d5c69c 100644
--- a/packages/backend-plugin-api/package.json
+++ b/packages/backend-plugin-api/package.json
@@ -1,17 +1,30 @@
{
"name": "@backstage/backend-plugin-api",
"description": "Core API used by Backstage backend plugins",
- "version": "0.5.5-next.0",
+ "version": "0.6.0",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
- "access": "public",
- "main": "dist/index.cjs.js",
- "types": "dist/index.d.ts"
+ "access": "public"
},
"backstage": {
"role": "node-library"
},
+ "exports": {
+ ".": "./src/index.ts",
+ "./alpha": "./src/alpha.ts",
+ "./package.json": "./package.json"
+ },
+ "typesVersions": {
+ "*": {
+ "alpha": [
+ "src/alpha.ts"
+ ],
+ "package.json": [
+ "package.json"
+ ]
+ }
+ },
"homepage": "https://backstage.io",
"repository": {
"type": "git",
diff --git a/packages/backend-plugin-api/src/alpha.ts b/packages/backend-plugin-api/src/alpha.ts
new file mode 100644
index 0000000000..baee739f49
--- /dev/null
+++ b/packages/backend-plugin-api/src/alpha.ts
@@ -0,0 +1,35 @@
+/*
+ * 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 {
+ BackendFeature,
+ createServiceRef,
+} from '@backstage/backend-plugin-api';
+
+/** @alpha */
+export interface FeatureDiscoveryService {
+ getBackendFeatures(): Promise<{ features: Array }>;
+}
+
+/**
+ * An optional service that can be used to dynamically load in additional BackendFeatures at runtime.
+ * @alpha
+ */
+export const featureDiscoveryServiceRef =
+ createServiceRef({
+ id: 'core.featureDiscovery',
+ scope: 'root',
+ });
diff --git a/packages/backend-plugin-api/src/index.ts b/packages/backend-plugin-api/src/index.ts
index 452fa046f9..8e0b23d20b 100644
--- a/packages/backend-plugin-api/src/index.ts
+++ b/packages/backend-plugin-api/src/index.ts
@@ -21,4 +21,5 @@
*/
export * from './services';
+export type { BackendFeature } from './types';
export * from './wiring';
diff --git a/packages/backend-plugin-api/src/services/definitions/ConfigService.ts b/packages/backend-plugin-api/src/services/definitions/RootConfigService.ts
similarity index 92%
rename from packages/backend-plugin-api/src/services/definitions/ConfigService.ts
rename to packages/backend-plugin-api/src/services/definitions/RootConfigService.ts
index db9aa88dda..02343e4ee8 100644
--- a/packages/backend-plugin-api/src/services/definitions/ConfigService.ts
+++ b/packages/backend-plugin-api/src/services/definitions/RootConfigService.ts
@@ -19,4 +19,4 @@ import { Config } from '@backstage/config';
/**
* @public
*/
-export interface ConfigService extends Config {}
+export interface RootConfigService extends Config {}
diff --git a/packages/backend-plugin-api/src/services/definitions/coreServices.ts b/packages/backend-plugin-api/src/services/definitions/coreServices.ts
index 7dad94cebf..b42795d052 100644
--- a/packages/backend-plugin-api/src/services/definitions/coreServices.ts
+++ b/packages/backend-plugin-api/src/services/definitions/coreServices.ts
@@ -32,13 +32,13 @@ export namespace coreServices {
});
/**
- * The service reference for the root scoped {@link ConfigService}.
+ * The service reference for the root scoped {@link RootConfigService}.
*
* @public
*/
- export const config = createServiceRef<
- import('./ConfigService').ConfigService
- >({ id: 'core.config', scope: 'root' });
+ export const rootConfig = createServiceRef<
+ import('./RootConfigService').RootConfigService
+ >({ id: 'core.rootConfig', scope: 'root' });
/**
* The service reference for the plugin scoped {@link DatabaseService}.
diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts
index ab0f0b84b1..98be8211db 100644
--- a/packages/backend-plugin-api/src/services/definitions/index.ts
+++ b/packages/backend-plugin-api/src/services/definitions/index.ts
@@ -20,7 +20,7 @@ export type {
CacheServiceOptions,
CacheServiceSetOptions,
} from './CacheService';
-export type { ConfigService } from './ConfigService';
+export type { RootConfigService } from './RootConfigService';
export type { DatabaseService } from './DatabaseService';
export type { DiscoveryService } from './DiscoveryService';
export type { HttpRouterService } from './HttpRouterService';
diff --git a/packages/backend-plugin-api/src/services/system/types.test.ts b/packages/backend-plugin-api/src/services/system/types.test.ts
index d3e9b9dd4e..4d0432505e 100644
--- a/packages/backend-plugin-api/src/services/system/types.test.ts
+++ b/packages/backend-plugin-api/src/services/system/types.test.ts
@@ -26,7 +26,7 @@ interface TestOptions {
function unused(..._any: any[]) {}
describe('createServiceFactory', () => {
- it('should create a sync factory with no options', () => {
+ it('should create a sync factory', () => {
const metaFactory = createServiceFactory({
service: ref,
deps: {},
@@ -51,7 +51,7 @@ describe('createServiceFactory', () => {
metaFactory();
});
- it('should create a sync root factory with no options', () => {
+ it('should create a sync root factory', () => {
const metaFactory = createServiceFactory({
service: rootDep,
deps: {},
@@ -75,7 +75,7 @@ describe('createServiceFactory', () => {
metaFactory();
});
- it('should create a factory with no options', () => {
+ it('should create a factory', () => {
const metaFactory = createServiceFactory({
service: ref,
deps: {},
@@ -100,7 +100,7 @@ describe('createServiceFactory', () => {
metaFactory();
});
- it('should create a factory with optional options', () => {
+ it('should create a factory with options', () => {
const metaFactory = createServiceFactory((_opts?: { x: number }) => ({
service: ref,
deps: {},
@@ -124,7 +124,8 @@ describe('createServiceFactory', () => {
metaFactory();
});
- it('should create a factory with required options', () => {
+ it('should not be allowed to require options', () => {
+ // @ts-expect-error
const metaFactory = createServiceFactory((_opts: { x: number }) => ({
service: ref,
deps: {},
@@ -134,23 +135,9 @@ describe('createServiceFactory', () => {
},
}));
expect(metaFactory).toEqual(expect.any(Function));
-
- // @ts-expect-error
- metaFactory('string');
- // @ts-expect-error
- metaFactory({});
- metaFactory({ x: 1 });
- // @ts-expect-error
- metaFactory({ x: 1, y: 2 });
- // @ts-expect-error
- metaFactory(null);
- // @ts-expect-error
- metaFactory(undefined);
- // @ts-expect-error
- metaFactory();
});
- it('should create a factory with optional options as interface', () => {
+ it('should create a factory with options as interface', () => {
const metaFactory = createServiceFactory((_opts?: TestOptions) => ({
service: ref,
deps: {},
@@ -174,32 +161,6 @@ describe('createServiceFactory', () => {
metaFactory();
});
- it('should create a factory with required options as interface', () => {
- const metaFactory = createServiceFactory((_opts: TestOptions) => ({
- service: ref,
- deps: {},
- async createRootContext() {},
- async factory() {
- return 'x';
- },
- }));
- expect(metaFactory).toEqual(expect.any(Function));
-
- // @ts-expect-error
- metaFactory('string');
- // @ts-expect-error
- metaFactory({});
- metaFactory({ x: 1 });
- // @ts-expect-error
- metaFactory({ x: 1, y: 2 });
- // @ts-expect-error
- metaFactory(null);
- // @ts-expect-error
- metaFactory(undefined);
- // @ts-expect-error
- metaFactory();
- });
-
it('should create root scoped factory with dependencies', () => {
const metaFactory = createServiceFactory({
service: createServiceRef({ id: 'foo', scope: 'root' }),
@@ -226,7 +187,7 @@ describe('createServiceFactory', () => {
metaFactory();
});
- it('should create root scoped factory with dependencies and optional options', () => {
+ it('should create root scoped factory with dependencies and options', () => {
const metaFactory = createServiceFactory((_options?: TestOptions) => ({
service: createServiceRef({ id: 'foo', scope: 'root' }),
deps: {
@@ -325,49 +286,7 @@ describe('createServiceFactory', () => {
metaFactory();
});
- it('should create factory with required options and dependencies', () => {
- const metaFactory = createServiceFactory((_opts: TestOptions) => ({
- service: ref,
- deps: {
- root: rootDep,
- plugin: pluginDep,
- },
- async createRootContext({ root }) {
- const root1: number = root;
- // @ts-expect-error
- const root2: string = root;
- unused(root1, root2);
- return { root };
- },
- async factory({ plugin }, { root }) {
- const root1: number = root;
- // @ts-expect-error
- const root2: string = root;
- const plugin3: boolean = plugin;
- // @ts-expect-error
- const plugin4: number = plugin;
- unused(root1, root2, plugin3, plugin4);
- return 'x';
- },
- }));
- expect(metaFactory).toEqual(expect.any(Function));
-
- // @ts-expect-error
- metaFactory('string');
- // @ts-expect-error
- metaFactory({});
- metaFactory({ x: 1 });
- // @ts-expect-error
- metaFactory({ x: 1, y: 2 });
- // @ts-expect-error
- metaFactory(null);
- // @ts-expect-error
- metaFactory(undefined);
- // @ts-expect-error
- metaFactory();
- });
-
- it('should create factory with optional options and dependencies', () => {
+ it('should create factory with options and dependencies', () => {
const metaFactory = createServiceFactory((_opts?: TestOptions) => ({
service: ref,
deps: {
diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts
index 190ef7c13e..bea53dc1ed 100644
--- a/packages/backend-plugin-api/src/services/system/types.ts
+++ b/packages/backend-plugin-api/src/services/system/types.ts
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+import { BackendFeature } from '../../types';
+
/**
* TODO
*
@@ -51,9 +53,7 @@ export type ServiceRef<
export interface ServiceFactory<
TService = unknown,
TScope extends 'plugin' | 'root' = 'plugin' | 'root',
-> {
- $$type: '@backstage/ServiceFactory';
-
+> extends BackendFeature {
service: ServiceRef;
}
@@ -191,20 +191,6 @@ export function createServiceFactory<
>(
config: (options?: TOpts) => RootServiceFactoryConfig,
): (options?: TOpts) => ServiceFactory;
-/**
- * Creates a root scoped service factory with required options.
- *
- * @public
- * @param config - The service factory configuration.
- */
-export function createServiceFactory<
- TService,
- TImpl extends TService,
- TDeps extends { [name in string]: ServiceRef },
- TOpts extends object | undefined = undefined,
->(
- config: (options: TOpts) => RootServiceFactoryConfig,
-): (options: TOpts) => ServiceFactory;
/**
* Creates a plugin scoped service factory without options.
*
@@ -237,25 +223,6 @@ export function createServiceFactory<
options?: TOpts,
) => PluginServiceFactoryConfig,
): (options?: TOpts) => ServiceFactory;
-/**
- * Creates a plugin scoped service factory with required options.
- *
- * @public
- * @param config - The service factory configuration.
- */
-export function createServiceFactory<
- TService,
- TImpl extends TService,
- TDeps extends { [name in string]: ServiceRef },
- TContext = undefined,
- TOpts extends object | undefined = undefined,
->(
- config:
- | PluginServiceFactoryConfig
- | ((
- options: TOpts,
- ) => PluginServiceFactoryConfig),
-): (options: TOpts) => ServiceFactory;
export function createServiceFactory<
TService,
TImpl extends TService,
@@ -281,7 +248,7 @@ export function createServiceFactory<
if (anyConf.service.scope === 'root') {
const c = anyConf as RootServiceFactoryConfig;
return {
- $$type: '@backstage/ServiceFactory',
+ $$type: '@backstage/BackendFeature',
version: 'v1',
service: c.service,
deps: c.deps,
@@ -295,7 +262,7 @@ export function createServiceFactory<
TDeps
>;
return {
- $$type: '@backstage/ServiceFactory',
+ $$type: '@backstage/BackendFeature',
version: 'v1',
service: c.service,
...('createRootContext' in c
diff --git a/packages/backend-plugin-api/src/types.ts b/packages/backend-plugin-api/src/types.ts
new file mode 100644
index 0000000000..59f1106fab
--- /dev/null
+++ b/packages/backend-plugin-api/src/types.ts
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/** @internal */
+export interface BackendFeatureFactory<
+ TOptions extends [options?: object] = [],
+> {
+ (...options: TOptions): BackendFeature;
+ $$type: '@backstage/BackendFeatureFactory';
+}
+
+/** @public */
+export interface BackendFeature {
+ // NOTE: This type is opaque in order to simplify future API evolution.
+ $$type: '@backstage/BackendFeature';
+}
diff --git a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.test.ts b/packages/backend-plugin-api/src/wiring/createSharedEnvironment.test.ts
deleted file mode 100644
index 381a4799d2..0000000000
--- a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.test.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2023 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {
- createServiceFactory,
- createServiceRef,
- ServiceFactoryOrFunction,
-} from '../services';
-import {
- createSharedEnvironment,
- InternalSharedBackendEnvironment,
-} from './createSharedEnvironment';
-
-const fooService = createServiceRef({ id: 'foo', scope: 'root' });
-const fooFactory = createServiceFactory({
- service: fooService,
- deps: {},
- async factory() {
- return 'foo';
- },
-});
-
-const barService = createServiceRef({ id: 'bar', scope: 'root' });
-const barFactory = createServiceFactory({
- service: barService,
- deps: {},
- async factory() {
- return 0xba5;
- },
-});
-
-describe('createSharedEnvironment', () => {
- it('should create an empty shared environment', () => {
- const env = createSharedEnvironment({});
- expect(env).toBeDefined();
- const internalEnv = env() as unknown as InternalSharedBackendEnvironment;
- expect(internalEnv).toEqual({
- $$type: '@backstage/SharedBackendEnvironment',
- version: 'v1',
- services: undefined,
- });
- });
-
- it('should create a shared environment with services', () => {
- const env = createSharedEnvironment({
- services: [fooFactory, barFactory()],
- });
- const internalEnv = env() as unknown as InternalSharedBackendEnvironment;
- expect(internalEnv.version).toBe('v1');
- expect(internalEnv.services?.length).toBe(2);
- expect(internalEnv.services?.[0]?.service.id).toBe('foo');
- expect(internalEnv.services?.[1]?.service.id).toBe('bar');
- });
-
- it('should create a shared environment with options', () => {
- const env = createSharedEnvironment((options?: { withFoo?: boolean }) => {
- const services = new Array();
- if (options?.withFoo) {
- services.push(fooFactory());
- }
- services.push(barFactory);
- return { services };
- });
- const internalEnv1 = env() as unknown as InternalSharedBackendEnvironment;
- expect(internalEnv1.version).toBe('v1');
- expect(internalEnv1.services?.length).toBe(1);
- expect(internalEnv1.services?.[0]?.service.id).toBe('bar');
-
- const internalEnv2 = env({
- withFoo: true,
- }) as unknown as InternalSharedBackendEnvironment;
- expect(internalEnv2.version).toBe('v1');
- expect(internalEnv2.services?.length).toBe(2);
- expect(internalEnv2.services?.[0]?.service.id).toBe('foo');
- expect(internalEnv2.services?.[1]?.service.id).toBe('bar');
- });
-
- it('should not allow duplicate service factories', () => {
- expect(() =>
- createSharedEnvironment({
- services: [fooFactory, fooFactory()],
- })(),
- ).toThrow(
- "Duplicate service implementations provided in shared environment for 'foo'",
- );
- });
-});
diff --git a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts b/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts
deleted file mode 100644
index 57f0533523..0000000000
--- a/packages/backend-plugin-api/src/wiring/createSharedEnvironment.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2023 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { ServiceFactory, ServiceFactoryOrFunction } from '../services';
-
-/**
- * The configuration options passed to {@link createSharedEnvironment}.
- *
- * @public
- */
-export interface SharedBackendEnvironmentConfig {
- services?: ServiceFactoryOrFunction[];
-}
-
-/**
- * An opaque type that represents the contents of a shared backend environment.
- *
- * @public
- */
-export interface SharedBackendEnvironment {
- $$type: '@backstage/SharedBackendEnvironment';
-
- // NOTE: This type is opaque in order to allow for future API evolution without
- // cluttering the external API. For example we might want to add support
- // for more powerful callback based backend modifications.
- //
- // By making this opaque we also ensure that the type doesn't become an input
- // type that we need to care about, as it would otherwise be possible to pass
- // a custom environment definition to `createBackend`, which we don't want.
-}
-
-/**
- * This type is NOT supposed to be used by anyone except internally by the
- * backend-app-api package.
- *
- * @internal
- */
-export interface InternalSharedBackendEnvironment {
- version: 'v1';
- services?: ServiceFactory[];
-}
-
-/**
- * Creates a shared backend environment which can be used to create multiple
- * backends.
- *
- * @public
- */
-export function createSharedEnvironment<
- TOptions extends [options?: object] = [],
->(
- config:
- | SharedBackendEnvironmentConfig
- | ((...params: TOptions) => SharedBackendEnvironmentConfig),
-): (...options: TOptions) => SharedBackendEnvironment {
- const configCallback = typeof config === 'function' ? config : () => config;
-
- return (...options) => {
- const actualConfig = configCallback(...options);
- const services = actualConfig?.services?.map(sf =>
- typeof sf === 'function' ? sf() : sf,
- );
-
- const exists = new Set();
- const duplicates = new Set();
- for (const { service } of services ?? []) {
- if (exists.has(service.id)) {
- duplicates.add(service.id);
- } else {
- exists.add(service.id);
- }
- }
-
- if (duplicates.size > 0) {
- const dupStr = [...duplicates].map(id => `'${id}'`).join(', ');
- throw new Error(
- `Duplicate service implementations provided in shared environment for ${dupStr}`,
- );
- }
-
- // Here to ensure type safety in this internal implementation.
- const env: SharedBackendEnvironment & InternalSharedBackendEnvironment = {
- $$type: '@backstage/SharedBackendEnvironment',
- version: 'v1',
- services,
- };
- return env;
- };
-}
diff --git a/packages/backend-plugin-api/src/wiring/factories.test.ts b/packages/backend-plugin-api/src/wiring/factories.test.ts
index ae199d84e4..1fd05a1605 100644
--- a/packages/backend-plugin-api/src/wiring/factories.test.ts
+++ b/packages/backend-plugin-api/src/wiring/factories.test.ts
@@ -33,22 +33,19 @@ describe('createExtensionPoint', () => {
describe('createBackendPlugin', () => {
it('should create a BackendPlugin', () => {
- const plugin = createBackendPlugin((_options: { a: string }) => ({
+ const plugin = createBackendPlugin({
pluginId: 'x',
register(r) {
r.registerInit({ deps: {}, async init() {} });
},
- }));
+ });
expect(plugin).toBeDefined();
- expect(plugin({ a: 'a' })).toBeDefined();
- expect(plugin({ a: 'a' })).toEqual({
+ expect(plugin()).toEqual({
$$type: '@backstage/BackendFeature',
version: 'v1',
getRegistrations: expect.any(Function),
});
- expect(
- (plugin({ a: 'a' }) as InternalBackendFeature).getRegistrations(),
- ).toEqual([
+ expect((plugin() as InternalBackendFeature).getRegistrations()).toEqual([
{
type: 'plugin',
pluginId: 'x',
@@ -60,95 +57,32 @@ describe('createBackendPlugin', () => {
},
]);
// @ts-expect-error
- expect(plugin()).toBeDefined();
- // @ts-expect-error
- expect(plugin({ b: 'b' })).toBeDefined();
- });
-
- it('should create plugins with optional options', () => {
- const plugin = createBackendPlugin((_options?: { a: string }) => ({
- pluginId: 'x',
- register() {},
- }));
- expect(plugin).toBeDefined();
expect(plugin({ a: 'a' })).toBeDefined();
- expect(plugin()).toBeDefined();
- // @ts-expect-error
- expect(plugin({ b: 'b' })).toBeDefined();
- });
-
- it('should create plugins without options', () => {
- const plugin = createBackendPlugin({
- pluginId: 'x',
- register() {},
- });
- expect(plugin).toBeDefined();
- // @ts-expect-error
- expect(plugin({ a: 'a' })).toBeDefined();
- // @ts-expect-error
- expect(plugin({})).toBeDefined();
- });
-
- it('should create a BackendPlugin with options as interface', () => {
- interface TestOptions {
- a: string;
- }
- const plugin = createBackendPlugin((_options: TestOptions) => ({
- pluginId: 'x',
- register() {},
- }));
- expect(plugin).toBeDefined();
- expect(plugin({ a: 'a' })).toBeDefined();
- expect(plugin({ a: 'a' })).toEqual({
- $$type: '@backstage/BackendFeature',
- version: 'v1',
- getRegistrations: expect.any(Function),
- });
- // @ts-expect-error
- expect(plugin()).toBeDefined();
- // @ts-expect-error
- expect(plugin({ b: 'b' })).toBeDefined();
- });
-
- it('should create plugins with optional options as interface', () => {
- interface TestOptions {
- a: string;
- }
- const plugin = createBackendPlugin((_options?: TestOptions) => ({
- pluginId: 'x',
- register() {},
- }));
- expect(plugin).toBeDefined();
- expect(plugin({ a: 'a' })).toBeDefined();
- expect(plugin()).toBeDefined();
- // @ts-expect-error
- expect(plugin({ b: 'b' })).toBeDefined();
});
});
describe('createBackendModule', () => {
it('should create a BackendModule', () => {
- const mod = createBackendModule((_options: { a: string }) => ({
+ const mod = createBackendModule({
pluginId: 'x',
moduleId: 'y',
register(r) {
r.registerInit({ deps: {}, async init() {} });
},
- }));
+ });
expect(mod).toBeDefined();
- expect(mod({ a: 'a' })).toBeDefined();
- expect(mod({ a: 'a' })).toEqual({
+ expect(mod()).toBeDefined();
+ expect(mod()).toEqual({
$$type: '@backstage/BackendFeature',
version: 'v1',
getRegistrations: expect.any(Function),
});
- expect(
- (mod({ a: 'a' }) as InternalBackendFeature).getRegistrations(),
- ).toEqual([
+ expect((mod() as InternalBackendFeature).getRegistrations()).toEqual([
{
type: 'module',
pluginId: 'x',
moduleId: 'y',
+ extensionPoints: [],
init: {
deps: expect.any(Object),
func: expect.any(Function),
@@ -156,72 +90,6 @@ describe('createBackendModule', () => {
},
]);
// @ts-expect-error
- expect(mod()).toBeDefined();
- // @ts-expect-error
- expect(mod({ b: 'b' })).toBeDefined();
- });
-
- it('should create modules with optional options', () => {
- const mod = createBackendModule((_options?: { a: string }) => ({
- pluginId: 'x',
- moduleId: 'y',
- register() {},
- }));
- expect(mod).toBeDefined();
expect(mod({ a: 'a' })).toBeDefined();
- expect(mod()).toBeDefined();
- // @ts-expect-error
- expect(mod({ b: 'b' })).toBeDefined();
- });
-
- it('should create modules without options', () => {
- const mod = createBackendModule({
- pluginId: 'x',
- moduleId: 'y',
- register() {},
- });
- expect(mod).toBeDefined();
- // @ts-expect-error
- expect(mod({ a: 'a' })).toBeDefined();
- // @ts-expect-error
- expect(mod({})).toBeDefined();
- });
-
- it('should create a BackendModule as interface', () => {
- interface TestOptions {
- a: string;
- }
- const mod = createBackendModule((_options: TestOptions) => ({
- pluginId: 'x',
- moduleId: 'y',
- register() {},
- }));
- expect(mod).toBeDefined();
- expect(mod({ a: 'a' })).toBeDefined();
- expect(mod({ a: 'a' })).toEqual({
- $$type: '@backstage/BackendFeature',
- version: 'v1',
- getRegistrations: expect.any(Function),
- });
- // @ts-expect-error
- expect(mod()).toBeDefined();
- // @ts-expect-error
- expect(mod({ b: 'b' })).toBeDefined();
- });
-
- it('should create modules with optional options as interface', () => {
- interface TestOptions {
- a: string;
- }
- const mod = createBackendModule((_options?: TestOptions) => ({
- pluginId: 'x',
- moduleId: 'y',
- register() {},
- }));
- expect(mod).toBeDefined();
- expect(mod({ a: 'a' })).toBeDefined();
- expect(mod()).toBeDefined();
- // @ts-expect-error
- expect(mod({ b: 'b' })).toBeDefined();
});
});
diff --git a/packages/backend-plugin-api/src/wiring/factories.ts b/packages/backend-plugin-api/src/wiring/factories.ts
index 928c933af0..9fa0f87bab 100644
--- a/packages/backend-plugin-api/src/wiring/factories.ts
+++ b/packages/backend-plugin-api/src/wiring/factories.ts
@@ -14,12 +14,11 @@
* limitations under the License.
*/
+import { BackendFeature, BackendFeatureFactory } from '../types';
import {
BackendModuleRegistrationPoints,
BackendPluginRegistrationPoints,
- BackendFeature,
ExtensionPoint,
- InternalBackendFeature,
InternalBackendModuleRegistration,
InternalBackendPluginRegistration,
} from './types';
@@ -85,13 +84,10 @@ export interface BackendPluginConfig {
* @see {@link https://backstage.io/docs/backend-system/architecture/plugins | The architecture of plugins}
* @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
*/
-export function createBackendPlugin(
- config: BackendPluginConfig | ((...params: TOptions) => BackendPluginConfig),
-): (...params: TOptions) => BackendFeature {
- const configCallback = typeof config === 'function' ? config : () => config;
- return (...options: TOptions): InternalBackendFeature => {
- const c = configCallback(...options);
-
+export function createBackendPlugin(
+ config: BackendPluginConfig,
+): () => BackendFeature {
+ const factory: BackendFeatureFactory = () => {
let registrations: InternalBackendPluginRegistration[];
return {
@@ -106,7 +102,7 @@ export function createBackendPlugin(
let init: InternalBackendPluginRegistration['init'] | undefined =
undefined;
- c.register({
+ config.register({
registerExtensionPoint(ext, impl) {
if (init) {
throw new Error(
@@ -128,14 +124,14 @@ export function createBackendPlugin(
if (!init) {
throw new Error(
- `registerInit was not called by register in ${c.pluginId}`,
+ `registerInit was not called by register in ${config.pluginId}`,
);
}
registrations = [
{
type: 'plugin',
- pluginId: c.pluginId,
+ pluginId: config.pluginId,
extensionPoints,
init,
},
@@ -144,6 +140,9 @@ export function createBackendPlugin(
},
};
};
+ factory.$$type = '@backstage/BackendFeatureFactory';
+
+ return factory;
}
/**
@@ -155,13 +154,14 @@ export function createBackendPlugin(
*/
export interface BackendModuleConfig {
/**
- * The ID of this plugin.
+ * Should exactly match the `id` of the plugin that the module extends.
*
* @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
*/
pluginId: string;
+
/**
- * Should exactly match the `id` of the plugin that the module extends.
+ * The ID of this module, used to identify the module and ensure that it is not installed twice.
*/
moduleId: string;
register(reg: BackendModuleRegistrationPoints): void;
@@ -174,13 +174,10 @@ export interface BackendModuleConfig {
* @see {@link https://backstage.io/docs/backend-system/architecture/modules | The architecture of modules}
* @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
*/
-export function createBackendModule(
- config: BackendModuleConfig | ((...params: TOptions) => BackendModuleConfig),
-): (...params: TOptions) => BackendFeature {
- const configCallback = typeof config === 'function' ? config : () => config;
- return (...options: TOptions): InternalBackendFeature => {
- const c = configCallback(...options);
-
+export function createBackendModule(
+ config: BackendModuleConfig,
+): () => BackendFeature {
+ const factory: BackendFeatureFactory = () => {
let registrations: InternalBackendModuleRegistration[];
return {
@@ -190,10 +187,20 @@ export function createBackendModule(
if (registrations) {
return registrations;
}
+ const extensionPoints: InternalBackendPluginRegistration['extensionPoints'] =
+ [];
let init: InternalBackendModuleRegistration['init'] | undefined =
undefined;
- c.register({
+ config.register({
+ registerExtensionPoint(ext, impl) {
+ if (init) {
+ throw new Error(
+ 'registerExtensionPoint called after registerInit',
+ );
+ }
+ extensionPoints.push([ext, impl]);
+ },
registerInit(regInit) {
if (init) {
throw new Error('registerInit must only be called once');
@@ -207,15 +214,16 @@ export function createBackendModule(
if (!init) {
throw new Error(
- `registerInit was not called by register in ${c.moduleId} module for ${c.pluginId}`,
+ `registerInit was not called by register in ${config.moduleId} module for ${config.pluginId}`,
);
}
registrations = [
{
type: 'module',
- pluginId: c.pluginId,
- moduleId: c.moduleId,
+ pluginId: config.pluginId,
+ moduleId: config.moduleId,
+ extensionPoints,
init,
},
];
@@ -223,4 +231,7 @@ export function createBackendModule(
},
};
};
+ factory.$$type = '@backstage/BackendFeatureFactory';
+
+ return factory;
}
diff --git a/packages/backend-plugin-api/src/wiring/index.ts b/packages/backend-plugin-api/src/wiring/index.ts
index 242301ea4d..49f15c0b55 100644
--- a/packages/backend-plugin-api/src/wiring/index.ts
+++ b/packages/backend-plugin-api/src/wiring/index.ts
@@ -14,11 +14,6 @@
* limitations under the License.
*/
-export { createSharedEnvironment } from './createSharedEnvironment';
-export type {
- SharedBackendEnvironment,
- SharedBackendEnvironmentConfig,
-} from './createSharedEnvironment';
export type {
BackendModuleConfig,
BackendPluginConfig,
@@ -32,6 +27,5 @@ export {
export type {
BackendModuleRegistrationPoints,
BackendPluginRegistrationPoints,
- BackendFeature,
ExtensionPoint,
} from './types';
diff --git a/packages/backend-plugin-api/src/wiring/types.ts b/packages/backend-plugin-api/src/wiring/types.ts
index c81ee3ceb7..55d43ccb7c 100644
--- a/packages/backend-plugin-api/src/wiring/types.ts
+++ b/packages/backend-plugin-api/src/wiring/types.ts
@@ -15,6 +15,7 @@
*/
import { ServiceRef } from '../services/system/types';
+import { BackendFeature } from '../types';
/**
* TODO
@@ -59,6 +60,10 @@ export interface BackendPluginRegistrationPoints {
* @public
*/
export interface BackendModuleRegistrationPoints {
+ registerExtensionPoint(
+ ref: ExtensionPoint,
+ impl: TExtensionPoint,
+ ): void;
registerInit(options: {
deps: {
[name in keyof Deps]: ServiceRef | ExtensionPoint;
@@ -67,12 +72,6 @@ export interface BackendModuleRegistrationPoints {
}): void;
}
-/** @public */
-export interface BackendFeature {
- // NOTE: This type is opaque in order to simplify future API evolution.
- $$type: '@backstage/BackendFeature';
-}
-
/** @internal */
export interface InternalBackendFeature extends BackendFeature {
version: 'v1';
@@ -97,6 +96,7 @@ export interface InternalBackendModuleRegistration {
pluginId: string;
moduleId: string;
type: 'module';
+ extensionPoints: Array, unknown]>;
init: {
deps: Record | ExtensionPoint>;
func(deps: Record): Promise;
diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md
index ec1dd319ea..11a039e053 100644
--- a/packages/backend-tasks/CHANGELOG.md
+++ b/packages/backend-tasks/CHANGELOG.md
@@ -1,5 +1,34 @@
# @backstage/backend-tasks
+## 0.5.5
+
+### Patch Changes
+
+- dfd1b6b2fc33: Make `readTaskScheduleDefinitionFromConfig` properly handle bad inputs
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
+## 0.5.5-next.2
+
+### Patch Changes
+
+- dfd1b6b2fc33: Make `readTaskScheduleDefinitionFromConfig` properly handle bad inputs
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.2
+
+## 0.5.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
## 0.5.5-next.0
### Patch Changes
diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json
index 4c0e4fd27e..ca9129078e 100644
--- a/packages/backend-tasks/package.json
+++ b/packages/backend-tasks/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-tasks",
"description": "Common distributed task management library for Backstage backends",
- "version": "0.5.5-next.0",
+ "version": "0.5.5",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts
index fc2a045614..21f51954b5 100644
--- a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts
+++ b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts
@@ -76,7 +76,7 @@ describe('readTaskScheduleDefinitionFromConfig', () => {
);
});
- it('invalid frequency value', () => {
+ it('invalid frequency key', () => {
const config = new ConfigReader({
frequency: {
invalid: 'value',
@@ -89,6 +89,19 @@ describe('readTaskScheduleDefinitionFromConfig', () => {
);
});
+ it('invalid frequency value', () => {
+ const config = new ConfigReader({
+ frequency: {
+ minutes: 'value',
+ },
+ timeout: 'PT3M',
+ });
+
+ expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow(
+ "Unable to convert config value for key 'frequency.minutes' in 'mock-config' to a number",
+ );
+ });
+
it('frequency value with additional invalid prop', () => {
const config = new ConfigReader({
frequency: {
diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts
index 1448a328f8..b31cf68f74 100644
--- a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts
+++ b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts
@@ -15,7 +15,7 @@
*/
import { Config } from '@backstage/config';
-import { HumanDuration, JsonObject } from '@backstage/types';
+import { HumanDuration } from '@backstage/types';
import { TaskScheduleDefinition } from './types';
import { Duration } from 'luxon';
@@ -31,29 +31,48 @@ const propsOfHumanDuration = [
];
function convertToHumanDuration(config: Config, key: string): HumanDuration {
- const props = config.getConfig(key).keys();
- if (!props.find(prop => propsOfHumanDuration.includes(prop))) {
+ // Ensures that the root is an object
+ const root = config.getConfig(key);
+
+ const result: Record = {};
+ let found = false;
+ for (const prop of propsOfHumanDuration) {
+ const value = root.getOptionalNumber(prop);
+ if (value !== undefined) {
+ result[prop] = value;
+ found = true;
+ }
+ }
+
+ if (!found) {
throw new Error(
`HumanDuration needs at least one of: ${propsOfHumanDuration}`,
);
}
- const invalidProps = props.filter(
- prop => !propsOfHumanDuration.includes(prop),
- );
+ const invalidProps = root
+ .keys()
+ .filter(prop => !propsOfHumanDuration.includes(prop));
if (invalidProps.length > 0) {
throw new Error(
`HumanDuration does not contain properties: ${invalidProps}`,
);
}
- return config.get(key) as HumanDuration;
+ return result as HumanDuration;
}
function readDuration(config: Config, key: string): Duration | HumanDuration {
- return typeof config.get(key) === 'string'
- ? Duration.fromISO(config.getString(key))
- : convertToHumanDuration(config, key);
+ if (typeof config.get(key) === 'string') {
+ const value = config.getString(key);
+ const duration = Duration.fromISO(value);
+ if (!duration.isValid) {
+ throw new Error(`Invalid duration: ${value}`);
+ }
+ return duration;
+ }
+
+ return convertToHumanDuration(config, key);
}
function readCronOrDuration(
diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md
index 2d40873bb9..d6eec9f7ed 100644
--- a/packages/backend-test-utils/CHANGELOG.md
+++ b/packages/backend-test-utils/CHANGELOG.md
@@ -1,5 +1,61 @@
# @backstage/backend-test-utils
+## 0.2.0
+
+### Minor Changes
+
+- b9c57a4f857e: **BREAKING**: Renamed `mockServices.config` to `mockServices.rootConfig`.
+- a6d7983f349c: **BREAKING**: Removed the `services` option from `createBackend`. Service factories are now `BackendFeature`s and should be installed with `backend.add(...)` instead. The following should be migrated:
+
+ ```ts
+ const backend = createBackend({ services: [myCustomServiceFactory] });
+ ```
+
+ To instead pass the service factory via `backend.add(...)`:
+
+ ```ts
+ const backend = createBackend();
+ backend.add(customRootLoggerServiceFactory);
+ ```
+
+### Patch Changes
+
+- ae9304818136: Add needed constants and constructs to support PostgreSQL version 14 as test database
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-app-api@0.5.0
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-auth-node@0.2.17
+ - @backstage/config@1.0.8
+ - @backstage/types@1.1.0
+
+## 0.2.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-app-api@0.5.0-next.2
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-auth-node@0.2.17-next.2
+
+## 0.2.0-next.1
+
+### Minor Changes
+
+- b9c57a4f857e: **BREAKING**: Renamed `mockServices.config` to `mockServices.rootConfig`.
+
+### Patch Changes
+
+- ae9304818136: Add needed constants and constructs to support PostgreSQL version 14 as test database
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-auth-node@0.2.17-next.1
+ - @backstage/backend-app-api@0.5.0-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/config@1.0.8
+ - @backstage/types@1.1.0
+
## 0.1.40-next.0
### Patch Changes
diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md
index 358b51071f..d086b1d2ae 100644
--- a/packages/backend-test-utils/api-report.md
+++ b/packages/backend-test-utils/api-report.md
@@ -6,7 +6,6 @@
import { Backend } from '@backstage/backend-app-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
import { CacheService } from '@backstage/backend-plugin-api';
-import { ConfigService } from '@backstage/backend-plugin-api';
import { DatabaseService } from '@backstage/backend-plugin-api';
import { ExtendedHttpServer } from '@backstage/backend-app-api';
import { ExtensionPoint } from '@backstage/backend-plugin-api';
@@ -18,10 +17,10 @@ import { Knex } from 'knex';
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 { RootLifecycleService } from '@backstage/backend-plugin-api';
import { SchedulerService } from '@backstage/backend-plugin-api';
import { ServiceFactory } from '@backstage/backend-plugin-api';
-import { ServiceRef } from '@backstage/backend-plugin-api';
import { TokenManagerService } from '@backstage/backend-plugin-api';
import { UrlReaderService } from '@backstage/backend-plugin-api';
@@ -36,19 +35,6 @@ export namespace mockServices {
factory: () => ServiceFactory;
}
// (undocumented)
- export function config(options?: config.Options): ConfigService;
- // (undocumented)
- export namespace config {
- // (undocumented)
- export type Options = {
- data?: JsonObject;
- };
- const // (undocumented)
- factory: (
- options?: Options | undefined,
- ) => ServiceFactory;
- }
- // (undocumented)
export namespace database {
const // (undocumented)
factory: () => ServiceFactory;
@@ -83,6 +69,19 @@ export namespace mockServices {
factory: () => ServiceFactory;
}
// (undocumented)
+ export function rootConfig(options?: rootConfig.Options): RootConfigService;
+ // (undocumented)
+ export namespace rootConfig {
+ // (undocumented)
+ export type Options = {
+ data?: JsonObject;
+ };
+ const // (undocumented)
+ factory: (
+ options?: Options | undefined,
+ ) => ServiceFactory;
+ }
+ // (undocumented)
export namespace rootLifecycle {
const // (undocumented)
factory: () => ServiceFactory;
@@ -127,11 +126,8 @@ export function setupRequestMockHandlers(worker: {
}): void;
// @public (undocumented)
-export function startTestBackend<
- TServices extends any[],
- TExtensionPoints extends any[],
->(
- options: TestBackendOptions,
+export function startTestBackend(
+ options: TestBackendOptions,
): Promise;
// @public (undocumented)
@@ -140,10 +136,7 @@ export interface TestBackend extends Backend {
}
// @public (undocumented)
-export interface TestBackendOptions<
- TServices extends any[],
- TExtensionPoints extends any[],
-> {
+export interface TestBackendOptions {
// (undocumented)
extensionPoints?: readonly [
...{
@@ -154,20 +147,12 @@ export interface TestBackendOptions<
},
];
// (undocumented)
- features?: BackendFeature[];
- // (undocumented)
- services?: readonly [
- ...{
- [index in keyof TServices]:
- | ServiceFactory
- | (() => ServiceFactory)
- | [ServiceRef, Partial];
- },
- ];
+ features?: Array BackendFeature)>;
}
// @public
export type TestDatabaseId =
+ | 'POSTGRES_14'
| 'POSTGRES_13'
| 'POSTGRES_12'
| 'POSTGRES_11'
diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json
index d9d904357b..02cb1d5cf9 100644
--- a/packages/backend-test-utils/package.json
+++ b/packages/backend-test-utils/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-test-utils",
"description": "Test helpers library for Backstage backends",
- "version": "0.1.40-next.0",
+ "version": "0.2.0",
"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
diff --git a/packages/backend-test-utils/src/database/TestDatabases.test.ts b/packages/backend-test-utils/src/database/TestDatabases.test.ts
index be29a42806..d766d38815 100644
--- a/packages/backend-test-utils/src/database/TestDatabases.test.ts
+++ b/packages/backend-test-utils/src/database/TestDatabases.test.ts
@@ -59,6 +59,37 @@ describe('TestDatabases', () => {
describe('each connect', () => {
const dbs = TestDatabases.create();
+ itIfDocker(
+ 'obeys a provided connection string for postgres 14',
+ async () => {
+ const { host, port, user, password, stop } =
+ await startPostgresContainer('postgres:14');
+
+ try {
+ // Leave a mark
+ process.env.BACKSTAGE_TEST_DATABASE_POSTGRES14_CONNECTION_STRING = `postgresql://${user}:${password}@${host}:${port}`;
+ const input = await dbs.init('POSTGRES_14');
+ await input.schema.createTable('a', table =>
+ table.string('x').primary(),
+ );
+ await input.insert({ x: 'y' }).into('a');
+
+ // Look for the mark
+ const database = input.client.config.connection.database;
+ const output = knexFactory({
+ client: 'pg',
+ connection: { host, port, user, password, database },
+ });
+ // eslint-disable-next-line jest/no-standalone-expect
+ await expect(output.select('x').from('a')).resolves.toEqual([
+ { x: 'y' },
+ ]);
+ } finally {
+ await stop();
+ }
+ },
+ );
+
itIfDocker(
'obeys a provided connection string for postgres 13',
async () => {
diff --git a/packages/backend-test-utils/src/database/types.ts b/packages/backend-test-utils/src/database/types.ts
index 8c1d6f5193..a7bfdeb12d 100644
--- a/packages/backend-test-utils/src/database/types.ts
+++ b/packages/backend-test-utils/src/database/types.ts
@@ -24,6 +24,7 @@ import { getDockerImageForName } from '../util/getDockerImageForName';
* @public
*/
export type TestDatabaseId =
+ | 'POSTGRES_14'
| 'POSTGRES_13'
| 'POSTGRES_12'
| 'POSTGRES_11'
@@ -46,6 +47,13 @@ export type Instance = {
export const allDatabases: Record =
Object.freeze({
+ POSTGRES_14: {
+ name: 'Postgres 14.x',
+ driver: 'pg',
+ dockerImageName: getDockerImageForName('postgres:14'),
+ connectionStringEnvironmentVariableName:
+ 'BACKSTAGE_TEST_DATABASE_POSTGRES14_CONNECTION_STRING',
+ },
POSTGRES_13: {
name: 'Postgres 13.x',
driver: 'pg',
diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts
index 8a3125214c..0455b9f1ae 100644
--- a/packages/backend-test-utils/src/next/services/mockServices.ts
+++ b/packages/backend-test-utils/src/next/services/mockServices.ts
@@ -15,7 +15,7 @@
*/
import {
- ConfigService,
+ RootConfigService,
coreServices,
createServiceFactory,
IdentityService,
@@ -61,13 +61,13 @@ function simpleFactory<
* @public
*/
export namespace mockServices {
- export function config(options?: config.Options): ConfigService {
+ export function rootConfig(options?: rootConfig.Options): RootConfigService {
return new ConfigReader(options?.data, 'mock-config');
}
- export namespace config {
+ export namespace rootConfig {
export type Options = { data?: JsonObject };
- export const factory = simpleFactory(coreServices.config, config);
+ export const factory = simpleFactory(coreServices.rootConfig, rootConfig);
}
export function rootLogger(options?: rootLogger.Options): LoggerService {
diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts
index aa93a7dae1..b127712927 100644
--- a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts
+++ b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts
@@ -31,7 +31,6 @@ import { startTestBackend } from './TestBackend';
let globalTestBackendHasBeenStopped = false;
beforeAll(async () => {
await startTestBackend({
- services: [],
features: [
createBackendModule({
moduleId: 'test.module',
@@ -46,7 +45,7 @@ beforeAll(async () => {
},
});
},
- })(),
+ }),
],
});
});
@@ -68,17 +67,37 @@ describe('TestBackend', () => {
const extensionPoint5 = createExtensionPoint({ id: 'b5' });
await expect(
startTestBackend({
- services: [
+ features: [
// @ts-expect-error
[extensionPoint1, { a: 'a' }],
- [serviceRef, { a: 'a' }],
- [serviceRef, { a: 'a', b: 'b' }],
- // @ts-expect-error
- [serviceRef, { c: 'c' }],
- // @ts-expect-error
- [serviceRef, { a: 'a', c: 'c' }],
- // @ts-expect-error
- [serviceRef, { a: 'a', b: 'b', c: 'c' }],
+ createServiceFactory(() => ({
+ service: serviceRef,
+ deps: {},
+ // @ts-expect-error
+ factory: async () => ({ a: 'a' }),
+ })),
+ createServiceFactory(() => ({
+ service: serviceRef,
+ deps: {},
+ factory: async () => ({ a: 'a', b: 'b' }),
+ })),
+ createServiceFactory(() => ({
+ service: serviceRef,
+ deps: {},
+ // @ts-expect-error
+ factory: async () => ({ c: 'c' }),
+ })),
+ createServiceFactory(() => ({
+ service: serviceRef,
+ deps: {},
+ // @ts-expect-error
+ factory: async () => ({ a: 'a', c: 'c' }),
+ })),
+ createServiceFactory(() => ({
+ service: serviceRef,
+ deps: {},
+ factory: async () => ({ a: 'a', b: 'b', c: 'c' }),
+ })),
],
extensionPoints: [
// @ts-expect-error
@@ -124,8 +143,7 @@ describe('TestBackend', () => {
});
await startTestBackend({
- services: [sf],
- features: [testModule()],
+ features: [testModule(), sf()],
});
expect(testFn).toHaveBeenCalledWith('winning');
@@ -150,7 +168,6 @@ describe('TestBackend', () => {
});
const backend = await startTestBackend({
- services: [],
features: [testModule()],
});
@@ -168,7 +185,7 @@ describe('TestBackend', () => {
env.registerInit({
deps: {
cache: coreServices.cache,
- config: coreServices.config,
+ config: coreServices.rootConfig,
database: coreServices.database,
discovery: coreServices.discovery,
httpRouter: coreServices.httpRouter,
@@ -192,7 +209,6 @@ describe('TestBackend', () => {
});
await startTestBackend({
- services: [],
features: [testPlugin()],
});
});
@@ -220,4 +236,89 @@ describe('TestBackend', () => {
expect(res.status).toEqual(200);
expect(res.body).toEqual({ message: 'pong' });
});
+
+ it('should provide extension point implementations', async () => {
+ expect.assertions(3);
+
+ const extensionPointA = createExtensionPoint({ id: 'a' });
+ const extensionPointB = createExtensionPoint({ id: 'b' });
+ await expect(
+ startTestBackend({
+ extensionPoints: [
+ [extensionPointA, 'a'],
+ [extensionPointB, 'b'],
+ ],
+ features: [
+ createBackendModule({
+ pluginId: 'testA',
+ moduleId: 'test',
+ register(reg) {
+ reg.registerInit({
+ deps: { ext: extensionPointA },
+ async init({ ext }) {
+ expect(ext).toBe('a');
+ },
+ });
+ },
+ }),
+ createBackendModule({
+ pluginId: 'testB',
+ moduleId: 'test',
+ register(reg) {
+ reg.registerInit({
+ deps: { ext: extensionPointB },
+ async init({ ext }) {
+ expect(ext).toBe('b');
+ },
+ });
+ },
+ }),
+ ],
+ }),
+ ).resolves.not.toBeUndefined();
+ });
+
+ it('should reject extension point used by multiple plugins', async () => {
+ const extensionPointA = createExtensionPoint({ id: 'a' });
+ await expect(
+ startTestBackend({
+ extensionPoints: [[extensionPointA, 'a']],
+ features: [
+ createBackendModule({
+ pluginId: 'testA',
+ moduleId: 'test',
+ register(reg) {
+ reg.registerInit({
+ deps: { ext: extensionPointA },
+ async init() {},
+ });
+ },
+ }),
+ createBackendModule({
+ pluginId: 'testB',
+ moduleId: 'test',
+ register(reg) {
+ reg.registerInit({
+ deps: { ext: extensionPointA },
+ async init() {},
+ });
+ },
+ }),
+ ],
+ }),
+ ).rejects.toThrow(
+ "Extension point registered for plugin 'testA' may not be used by module for plugin 'testB'",
+ );
+ });
+
+ it('should reject extension point not used by any plugin', async () => {
+ const extensionPointA = createExtensionPoint({ id: 'a' });
+ await expect(
+ startTestBackend({
+ extensionPoints: [[extensionPointA, 'a']],
+ }),
+ ).rejects.toThrow(
+ "Unable to determine the plugin ID of extension point(s) 'a'. Tested extension points must be depended on by one or more tested modules.",
+ );
+ });
});
diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts
index e8df15862f..97da3543ae 100644
--- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts
+++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts
@@ -24,31 +24,21 @@ import {
} from '@backstage/backend-app-api';
import { HostDiscovery } from '@backstage/backend-common';
import {
- ServiceFactory,
- ServiceRef,
createServiceFactory,
BackendFeature,
ExtensionPoint,
coreServices,
- createBackendPlugin,
+ createBackendModule,
} from '@backstage/backend-plugin-api';
import { mockServices } from '../services';
import { ConfigReader } from '@backstage/config';
import express from 'express';
+// Direct internal import to avoid duplication
+// eslint-disable-next-line @backstage/no-forbidden-package-imports
+import { InternalBackendFeature } from '@backstage/backend-plugin-api/src/wiring/types';
/** @public */
-export interface TestBackendOptions<
- TServices extends any[],
- TExtensionPoints extends any[],
-> {
- services?: readonly [
- ...{
- [index in keyof TServices]:
- | ServiceFactory
- | (() => ServiceFactory)
- | [ServiceRef, Partial];
- },
- ];
+export interface TestBackendOptions {
extensionPoints?: readonly [
...{
[index in keyof TExtensionPoints]: [
@@ -57,7 +47,7 @@ export interface TestBackendOptions<
];
},
];
- features?: BackendFeature[];
+ features?: Array BackendFeature)>;
}
/** @public */
@@ -73,7 +63,7 @@ export interface TestBackend extends Backend {
const defaultServiceFactories = [
mockServices.cache.factory(),
- mockServices.config.factory(),
+ mockServices.rootConfig.factory(),
mockServices.database.factory(),
mockServices.httpRouter.factory(),
mockServices.identity.factory(),
@@ -87,28 +77,115 @@ const defaultServiceFactories = [
mockServices.urlReader.factory(),
];
+/**
+ * Given a set of extension points and plugins, find
+ * @returns
+ */
+function createExtensionPointTestModules(
+ features: Array BackendFeature)>,
+ extensionPointTuples?: readonly [
+ ref: ExtensionPoint,
+ impl: unknown,
+ ][],
+): Array<() => BackendFeature> {
+ if (!extensionPointTuples) {
+ return [];
+ }
+
+ const registrations = features.flatMap(featureOrFunction => {
+ const feature =
+ typeof featureOrFunction === 'function'
+ ? featureOrFunction()
+ : featureOrFunction;
+
+ if (feature.$$type !== '@backstage/BackendFeature') {
+ throw new Error(
+ `Failed to add feature, invalid type '${feature.$$type}'`,
+ );
+ }
+
+ if (isInternalBackendFeature(feature)) {
+ if (feature.version !== 'v1') {
+ throw new Error(
+ `Failed to add feature, invalid version '${feature.version}'`,
+ );
+ }
+ return feature.getRegistrations();
+ }
+ return [];
+ });
+
+ const extensionPointMap = new Map(
+ extensionPointTuples.map(ep => [ep[0].id, ep]),
+ );
+ const extensionPointsToSort = new Set(extensionPointMap.keys());
+ const extensionPointsByPlugin = new Map();
+
+ for (const registration of registrations) {
+ if (registration.type === 'module') {
+ const testDep = Object.values(registration.init.deps).filter(dep =>
+ extensionPointsToSort.has(dep.id),
+ );
+ if (testDep.length > 0) {
+ let points = extensionPointsByPlugin.get(registration.pluginId);
+ if (!points) {
+ points = [];
+ extensionPointsByPlugin.set(registration.pluginId, points);
+ }
+ for (const { id } of testDep) {
+ points.push(id);
+ extensionPointsToSort.delete(id);
+ }
+ }
+ }
+ }
+
+ if (extensionPointsToSort.size > 0) {
+ const list = Array.from(extensionPointsToSort)
+ .map(id => `'${id}'`)
+ .join(', ');
+ throw new Error(
+ `Unable to determine the plugin ID of extension point(s) ${list}. ` +
+ 'Tested extension points must be depended on by one or more tested modules.',
+ );
+ }
+
+ const modules = [];
+
+ for (const [pluginId, pluginExtensionPointIds] of extensionPointsByPlugin) {
+ modules.push(
+ createBackendModule({
+ pluginId,
+ moduleId: 'testExtensionPointRegistration',
+ register(reg) {
+ for (const id of pluginExtensionPointIds) {
+ const tuple = extensionPointMap.get(id)!;
+ reg.registerExtensionPoint(...tuple);
+ }
+
+ reg.registerInit({ deps: {}, async init() {} });
+ },
+ }),
+ );
+ }
+
+ return modules;
+}
+
const backendInstancesToCleanUp = new Array();
/** @public */
-export async function startTestBackend<
- TServices extends any[],
- TExtensionPoints extends any[],
->(
- options: TestBackendOptions,
+export async function startTestBackend(
+ options: TestBackendOptions,
): Promise {
- const {
- services = [],
- extensionPoints = [],
- features = [],
- ...otherOptions
- } = options;
+ const { extensionPoints, features = [], ...otherOptions } = options;
let server: ExtendedHttpServer;
const rootHttpRouterFactory = createServiceFactory({
service: coreServices.rootHttpRouter,
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
lifecycle: coreServices.rootLifecycle,
rootLogger: coreServices.rootLogger,
},
@@ -157,55 +234,20 @@ export async function startTestBackend<
},
});
- const factories = services.map(serviceDef => {
- if (Array.isArray(serviceDef)) {
- // if type is ExtensionPoint?
- // do something differently?
- const [ref, impl] = serviceDef;
- if (ref.scope === 'plugin') {
- return createServiceFactory({
- service: ref as ServiceRef,
- deps: {},
- factory: async () => impl,
- })();
- }
- return createServiceFactory({
- service: ref as ServiceRef,
- deps: {},
- factory: async () => impl,
- })();
- }
- if (typeof serviceDef === 'function') {
- return serviceDef();
- }
- return serviceDef as ServiceFactory;
- });
-
- for (const factory of defaultServiceFactories) {
- if (!factories.some(f => f.service.id === factory.service.id)) {
- factories.push(factory);
- }
- }
-
const backend = createSpecializedBackend({
...otherOptions,
- services: [...factories, rootHttpRouterFactory, discoveryFactory],
+ defaultServiceFactories: [
+ ...defaultServiceFactories,
+ rootHttpRouterFactory,
+ discoveryFactory,
+ ],
});
backendInstancesToCleanUp.push(backend);
- backend.add(
- createBackendPlugin({
- pluginId: `---test-extension-point-registrar`,
- register(reg) {
- for (const [ref, impl] of extensionPoints) {
- reg.registerExtensionPoint(ref, impl);
- }
-
- reg.registerInit({ deps: {}, async init() {} });
- },
- })(),
- );
+ for (const m of createExtensionPointTestModules(features, extensionPoints)) {
+ backend.add(m);
+ }
for (const feature of features) {
backend.add(feature);
@@ -248,3 +290,11 @@ function registerTestHooks() {
}
registerTestHooks();
+
+function isInternalBackendFeature(
+ feature: BackendFeature,
+): feature is InternalBackendFeature {
+ return (
+ typeof (feature as InternalBackendFeature).getRegistrations === 'function'
+ );
+}
diff --git a/packages/backend/.snyk b/packages/backend/.snyk
new file mode 100644
index 0000000000..ea5abd653d
--- /dev/null
+++ b/packages/backend/.snyk
@@ -0,0 +1,10 @@
+# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
+version: v1.22.1
+# ignores vulnerabilities until expiry date; change duration by modifying expiry date
+ignore:
+ SNYK-JS-ISOLATEDVM-3037320:
+ - '*':
+ reason: We do not pass any V8 cache data, and are therefore unaffected by this vulnerability
+ expires: 2033-07-02T16:55:57.077Z
+ created: 2023-07-02T16:55:57.077Z
+patch: {}
diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md
index d4359d6ece..76ac221f69 100644
--- a/packages/backend/CHANGELOG.md
+++ b/packages/backend/CHANGELOG.md
@@ -1,5 +1,164 @@
# example-backend
+## 0.2.86
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-search-backend-module-elasticsearch@1.3.3
+ - @backstage/plugin-search-backend-module-pg@0.5.9
+ - @backstage/plugin-azure-devops-backend@0.3.27
+ - @backstage/plugin-kubernetes-backend@0.11.3
+ - @backstage/plugin-lighthouse-backend@0.2.4
+ - @backstage/plugin-permission-backend@0.5.23
+ - @backstage/plugin-scaffolder-backend@1.16.0
+ - @backstage/plugin-devtools-backend@0.1.3
+ - @backstage/plugin-techdocs-backend@1.6.5
+ - @backstage/backend-common@0.19.2
+ - @backstage/plugin-catalog-backend@1.12.0
+ - @backstage/plugin-badges-backend@0.2.3
+ - @backstage/plugin-events-backend@0.2.9
+ - @backstage/plugin-search-backend@1.4.0
+ - @backstage/plugin-kafka-backend@0.2.41
+ - @backstage/plugin-proxy-backend@0.3.0
+ - @backstage/plugin-todo-backend@0.2.0
+ - @backstage/plugin-app-backend@0.3.48
+ - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.1
+ - @backstage/plugin-auth-backend@0.18.6
+ - @backstage/plugin-explore-backend@0.0.10
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0
+ - @backstage/plugin-scaffolder-backend-module-rails@0.4.17
+ - @backstage/plugin-entity-feedback-backend@0.1.6
+ - @backstage/plugin-code-coverage-backend@0.2.14
+ - @backstage/plugin-search-backend-node@1.2.4
+ - @backstage/plugin-linguist-backend@0.4.0
+ - @backstage/plugin-playlist-backend@0.3.4
+ - @backstage/plugin-jenkins-backend@0.2.3
+ - @backstage/plugin-nomad-backend@0.1.2
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/plugin-events-node@0.2.9
+ - @backstage/plugin-auth-node@0.2.17
+ - @backstage/integration@1.6.0
+ - @backstage/backend-tasks@0.5.5
+ - example-app@0.2.86
+ - @backstage/plugin-adr-backend@0.3.6
+ - @backstage/plugin-azure-sites-backend@0.1.10
+ - @backstage/plugin-graphql-backend@0.1.38
+ - @backstage/plugin-permission-node@0.7.11
+ - @backstage/plugin-rollbar-backend@0.1.45
+ - @backstage/plugin-tech-insights-backend@0.5.14
+ - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.32
+ - @backstage/plugin-tech-insights-node@0.4.6
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/plugin-permission-common@0.7.7
+ - @backstage/plugin-search-common@1.2.5
+
+## 0.2.86-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.18.6-next.2
+ - @backstage/plugin-scaffolder-backend@1.15.2-next.2
+ - @backstage/plugin-explore-backend@0.0.10-next.2
+ - @backstage/plugin-catalog-backend@1.12.0-next.2
+ - @backstage/plugin-proxy-backend@0.3.0-next.2
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/plugin-app-backend@0.3.48-next.2
+ - @backstage/plugin-linguist-backend@0.4.0-next.2
+ - @backstage/plugin-techdocs-backend@1.6.5-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - example-app@0.2.86-next.2
+ - @backstage/plugin-adr-backend@0.3.6-next.2
+ - @backstage/plugin-azure-devops-backend@0.3.27-next.2
+ - @backstage/plugin-badges-backend@0.2.3-next.2
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+ - @backstage/plugin-devtools-backend@0.1.3-next.2
+ - @backstage/plugin-entity-feedback-backend@0.1.6-next.2
+ - @backstage/plugin-events-backend@0.2.9-next.2
+ - @backstage/plugin-events-node@0.2.9-next.2
+ - @backstage/plugin-kafka-backend@0.2.41-next.2
+ - @backstage/plugin-kubernetes-backend@0.11.3-next.2
+ - @backstage/plugin-lighthouse-backend@0.2.4-next.2
+ - @backstage/plugin-permission-backend@0.5.23-next.2
+ - @backstage/plugin-permission-node@0.7.11-next.2
+ - @backstage/plugin-search-backend@1.4.0-next.2
+ - @backstage/plugin-search-backend-module-elasticsearch@1.3.3-next.2
+ - @backstage/plugin-search-backend-module-pg@0.5.9-next.2
+ - @backstage/plugin-search-backend-node@1.2.4-next.2
+ - @backstage/plugin-todo-backend@0.2.0-next.2
+ - @backstage/plugin-tech-insights-backend@0.5.14-next.2
+ - @backstage/plugin-tech-insights-node@0.4.6-next.2
+ - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.1-next.2
+ - @backstage/plugin-scaffolder-backend-module-rails@0.4.17-next.2
+ - @backstage/plugin-auth-node@0.2.17-next.2
+ - @backstage/plugin-azure-sites-backend@0.1.10-next.2
+ - @backstage/plugin-code-coverage-backend@0.2.14-next.2
+ - @backstage/plugin-graphql-backend@0.1.38-next.2
+ - @backstage/plugin-jenkins-backend@0.2.3-next.2
+ - @backstage/plugin-nomad-backend@0.1.2-next.2
+ - @backstage/plugin-playlist-backend@0.3.4-next.2
+ - @backstage/plugin-rollbar-backend@0.1.45-next.2
+ - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.32-next.2
+
+## 0.2.86-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-search-backend-module-elasticsearch@1.3.3-next.1
+ - @backstage/plugin-search-backend-module-pg@0.5.9-next.1
+ - @backstage/plugin-azure-devops-backend@0.3.27-next.1
+ - @backstage/plugin-kubernetes-backend@0.11.3-next.1
+ - @backstage/plugin-lighthouse-backend@0.2.4-next.1
+ - @backstage/plugin-permission-backend@0.5.23-next.1
+ - @backstage/plugin-scaffolder-backend@1.15.2-next.1
+ - @backstage/plugin-devtools-backend@0.1.3-next.1
+ - @backstage/plugin-techdocs-backend@1.6.5-next.1
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-catalog-backend@1.12.0-next.1
+ - @backstage/plugin-badges-backend@0.2.3-next.1
+ - @backstage/plugin-events-backend@0.2.9-next.1
+ - @backstage/plugin-search-backend@1.4.0-next.1
+ - @backstage/plugin-kafka-backend@0.2.41-next.1
+ - @backstage/plugin-proxy-backend@0.2.42-next.1
+ - @backstage/plugin-todo-backend@0.2.0-next.1
+ - @backstage/plugin-app-backend@0.3.48-next.1
+ - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.1-next.1
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.2.0-next.1
+ - @backstage/plugin-scaffolder-backend-module-rails@0.4.17-next.1
+ - @backstage/plugin-entity-feedback-backend@0.1.6-next.1
+ - @backstage/plugin-code-coverage-backend@0.2.14-next.1
+ - @backstage/plugin-search-backend-node@1.2.4-next.1
+ - @backstage/plugin-linguist-backend@0.3.2-next.1
+ - @backstage/plugin-playlist-backend@0.3.4-next.1
+ - @backstage/plugin-explore-backend@0.0.10-next.1
+ - @backstage/plugin-jenkins-backend@0.2.3-next.1
+ - @backstage/plugin-nomad-backend@0.1.2-next.1
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/plugin-events-node@0.2.9-next.1
+ - @backstage/plugin-auth-node@0.2.17-next.1
+ - @backstage/plugin-auth-backend@0.18.6-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/plugin-adr-backend@0.3.6-next.1
+ - @backstage/plugin-azure-sites-backend@0.1.10-next.1
+ - @backstage/plugin-graphql-backend@0.1.38-next.1
+ - @backstage/plugin-permission-node@0.7.11-next.1
+ - @backstage/plugin-rollbar-backend@0.1.45-next.1
+ - @backstage/plugin-tech-insights-backend@0.5.14-next.1
+ - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.32-next.1
+ - @backstage/plugin-tech-insights-node@0.4.6-next.1
+ - example-app@0.2.86-next.1
+ - @backstage/integration@1.5.1
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/plugin-permission-common@0.7.7
+ - @backstage/plugin-search-common@1.2.5
+
## 0.2.86-next.0
### Patch Changes
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 1c99a1bdf3..9e0896233d 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "example-backend",
- "version": "0.2.86-next.0",
+ "version": "0.2.86",
"main": "dist/index.cjs.js",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/packages/cli-node/CHANGELOG.md b/packages/cli-node/CHANGELOG.md
index fc2876fa76..8a7af72d8d 100644
--- a/packages/cli-node/CHANGELOG.md
+++ b/packages/cli-node/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/cli-node
+## 0.1.3
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/cli-common@0.1.12
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
+## 0.1.3-next.0
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/cli-common@0.1.12
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
## 0.1.2
### Patch Changes
diff --git a/packages/cli-node/package.json b/packages/cli-node/package.json
index c59a8930c0..589139a141 100644
--- a/packages/cli-node/package.json
+++ b/packages/cli-node/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli-node",
"description": "Node.js library for Backstage CLIs",
- "version": "0.1.2",
+ "version": "0.1.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -13,6 +13,12 @@
"backstage": {
"role": "node-library"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "packages/cli-node"
+ },
"scripts": {
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md
index 2652a40bde..9294e55efd 100644
--- a/packages/cli/CHANGELOG.md
+++ b/packages/cli/CHANGELOG.md
@@ -1,5 +1,41 @@
# @backstage/cli
+## 0.22.10
+
+### Patch Changes
+
+- 3f67cefb4780: Reload the frontend when app config changes
+- cebbf8a27f3c: Enable to print the config schema not merged with the `--no-merge` flag
+- 5c28ebc79fd6: Updated dependency `esbuild` to `^0.19.0`.
+- 971bdd6a4732: Bumped internal `nodemon` dependency.
+- Updated dependencies
+ - @backstage/config-loader@1.4.0
+ - @backstage/cli-node@0.1.3
+ - @backstage/integration@1.6.0
+ - @backstage/catalog-model@1.4.1
+ - @backstage/cli-common@0.1.12
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/eslint-plugin@0.1.3
+ - @backstage/release-manifests@0.0.9
+ - @backstage/types@1.1.0
+
+## 0.22.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config-loader@1.4.0-next.1
+ - @backstage/cli-node@0.1.3-next.0
+ - @backstage/integration@1.5.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/cli-common@0.1.12
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/eslint-plugin@0.1.3
+ - @backstage/release-manifests@0.0.9
+ - @backstage/types@1.1.0
+
## 0.22.10-next.0
### Patch Changes
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 8b78b16900..95095fe78c 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/cli",
"description": "CLI for developing Backstage plugins and apps",
- "version": "0.22.10-next.0",
+ "version": "0.22.10",
"publishConfig": {
"access": "public"
},
@@ -78,7 +78,7 @@
"cross-spawn": "^7.0.3",
"css-loader": "^6.5.1",
"diff": "^5.0.0",
- "esbuild": "^0.18.0",
+ "esbuild": "^0.19.0",
"esbuild-loader": "^2.18.0",
"eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0",
@@ -167,7 +167,7 @@
"del": "^7.0.0",
"mock-fs": "^5.1.0",
"msw": "^1.0.0",
- "nodemon": "^2.0.2",
+ "nodemon": "^3.0.1",
"ts-node": "^10.0.0",
"type-fest": "^2.19.0"
},
diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md
index 0782876942..909bb71b37 100644
--- a/packages/config-loader/CHANGELOG.md
+++ b/packages/config-loader/CHANGELOG.md
@@ -1,5 +1,89 @@
# @backstage/config-loader
+## 1.4.0
+
+### Minor Changes
+
+- 2f1859585998: Loading invalid TypeScript configuration schemas will now throw an error rather than silently being ignored.
+
+ In particular this includes defining any additional types other than `Config` in the schema file, or use of unsupported types such as `Record` or `Partial`.
+
+- cd514545d1d0: Adds a new `deepVisibility` schema keyword that sets child visibility recursively to the defined value, respecting preexisting values or child `deepVisibility`.
+
+ Example usage:
+
+ ```ts
+ export interface Config {
+ /**
+ * Enforces a default of `secret` instead of `backend` for this object.
+ * @deepVisibility secret
+ */
+ mySecretProperty: {
+ type: 'object';
+ properties: {
+ secretValue: {
+ type: 'string';
+ };
+
+ verySecretProperty: {
+ type: 'string';
+ };
+ };
+ };
+ }
+ ```
+
+ Example of a schema that would not be allowed:
+
+ ```ts
+ export interface Config {
+ /**
+ * Set the top level property to secret, enforcing a default of `secret` instead of `backend` for this object.
+ * @deepVisibility secret
+ */
+ mySecretProperty: {
+ type: 'object';
+ properties: {
+ frontendUrl: {
+ /**
+ * We can NOT override the visibility to reveal a property to the front end.
+ * @visibility frontend
+ */
+ type: 'string';
+ };
+
+ verySecretProperty: {
+ type: 'string';
+ };
+ };
+ };
+ }
+ ```
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.1.12
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
+## 1.4.0-next.1
+
+### Minor Changes
+
+- 2f1859585998: Loading invalid TypeScript configuration schemas will now throw an error rather than silently being ignored.
+
+ In particular this includes defining any additional types other than `Config` in the schema file, or use of unsupported types such as `Record` or `Partial`.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.1.12
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
## 1.4.0-next.0
### Minor Changes
diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json
index 51a4a5a6cd..7fba2d8acb 100644
--- a/packages/config-loader/package.json
+++ b/packages/config-loader/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/config-loader",
"description": "Config loading functionality used by Backstage backend, and CLI",
- "version": "1.4.0-next.0",
+ "version": "1.4.0",
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
diff --git a/packages/config-loader/src/schema/collect.ts b/packages/config-loader/src/schema/collect.ts
index ac3e4af341..7120c12d90 100644
--- a/packages/config-loader/src/schema/collect.ts
+++ b/packages/config-loader/src/schema/collect.ts
@@ -15,6 +15,7 @@
*/
import fs from 'fs-extra';
+import { EOL } from 'os';
import {
resolve as resolvePath,
relative as relativePath,
@@ -163,7 +164,7 @@ async function compileTsSchemas(paths: string[]) {
// Lazy loaded, because this brings up all of TypeScript and we don't
// want that eagerly loaded in tests
- const { getProgramFromFiles, generateSchema } = await import(
+ const { getProgramFromFiles, buildGenerator } = await import(
'typescript-json-schema'
);
@@ -183,17 +184,40 @@ async function compileTsSchemas(paths: string[]) {
const tsSchemas = paths.map(path => {
let value;
try {
- value = generateSchema(
+ const generator = buildGenerator(
program,
- // All schemas should export a `Config` symbol
- 'Config',
// This enables the use of these tags in TSDoc comments
{
required: true,
validationKeywords: ['visibility', 'deepVisibility', 'deprecated'],
},
[path.split(sep).join('/')], // Unix paths are expected for all OSes here
- ) as JsonObject | null;
+ );
+
+ // All schemas should export a `Config` symbol
+ value = generator?.getSchemaForSymbol('Config') as JsonObject | null;
+
+ // This makes sure that no additional symbols are defined in the schema. We don't allow
+ // this because they share a global namespace and will be merged together, leading to
+ // unpredictable behavior.
+ const userSymbols = new Set(generator?.getUserSymbols());
+ userSymbols.delete('Config');
+ if (userSymbols.size !== 0) {
+ const names = Array.from(userSymbols).join("', '");
+ throw new Error(
+ `Invalid configuration schema in ${path}, additional symbol definitions are not allowed, found '${names}'`,
+ );
+ }
+
+ // This makes sure that no unsupported types are used in the schema, for example `Record<,>`.
+ // The generator will extract these as a schema reference, which will in turn be broken for our usage.
+ const reffedDefs = Object.keys(generator?.ReffedDefinitions ?? {});
+ if (reffedDefs.length !== 0) {
+ const lines = reffedDefs.join(`${EOL} `);
+ throw new Error(
+ `Invalid configuration schema in ${path}, the following definitions are not supported:${EOL}${EOL} ${lines}`,
+ );
+ }
} catch (error) {
assertError(error);
if (error.message !== 'type Config not found') {
diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md
index 1de645fb9e..b09076f773 100644
--- a/packages/core-app-api/CHANGELOG.md
+++ b/packages/core-app-api/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/core-app-api
+## 1.9.1
+
+### Patch Changes
+
+- 9ae4e7e63836: Fixed a bug that could cause `navigate` analytics events to be misattributed to the plugin mounted on the root route (e.g. the `home` plugin at `/`) when the route that was navigated to wasn't associated with a routable extension.
+- Updated dependencies
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/config@1.0.8
+ - @backstage/types@1.1.0
+ - @backstage/version-bridge@1.0.4
+
## 1.9.1-next.0
### Patch Changes
diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json
index 15dca326e0..ff43d81329 100644
--- a/packages/core-app-api/package.json
+++ b/packages/core-app-api/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-app-api",
"description": "Core app API used by Backstage apps",
- "version": "1.9.1-next.0",
+ "version": "1.9.1",
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md
index 4ebd92ceb6..af5e2b8d6a 100644
--- a/packages/core-components/CHANGELOG.md
+++ b/packages/core-components/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/core-components
+## 0.13.4
+
+### Patch Changes
+
+- 3d86be999fdf: Prefer simple `theme.spacing` without string interpolation
+- Updated dependencies
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/theme@0.4.1
+ - @backstage/version-bridge@1.0.4
+
## 0.13.4-next.0
### Patch Changes
diff --git a/packages/core-components/package.json b/packages/core-components/package.json
index c9b1584b85..641a4e7270 100644
--- a/packages/core-components/package.json
+++ b/packages/core-components/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-components",
"description": "Core components used by Backstage plugins and apps",
- "version": "0.13.4-next.0",
+ "version": "0.13.4",
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md
index 9a65a15f98..9ddd48393b 100644
--- a/packages/create-app/CHANGELOG.md
+++ b/packages/create-app/CHANGELOG.md
@@ -1,5 +1,32 @@
# @backstage/create-app
+## 0.5.4
+
+### Patch Changes
+
+- b441642fbe0d: Bumped create-app version.
+- 572abc7edf55: Bumped create-app version.
+- 74f77f151a96: Bumped create-app version.
+- 5cc0ac5ef3d1: Bump to a newer version of the `concurrently` library
+- 46c9a798e41d: Updated the `app-config.yaml` template to use `proxy.endpoints`.
+- 971bdd6a4732: Bumped internal `nodemon` dependency.
+- Updated dependencies
+ - @backstage/cli-common@0.1.12
+
+## 0.5.4-next.2
+
+### Patch Changes
+
+- Bumped create-app version.
+
+## 0.5.4-next.1
+
+### Patch Changes
+
+- Bumped create-app version.
+- Updated dependencies
+ - @backstage/cli-common@0.1.12
+
## 0.5.4-next.0
### Patch Changes
diff --git a/packages/create-app/package.json b/packages/create-app/package.json
index fb203edf26..4f629d951e 100644
--- a/packages/create-app/package.json
+++ b/packages/create-app/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/create-app",
"description": "A CLI that helps you create your own Backstage app",
- "version": "0.5.4-next.0",
+ "version": "0.5.4",
"publishConfig": {
"access": "public"
},
@@ -49,7 +49,7 @@
"@types/node": "^16.11.26",
"@types/recursive-readdir": "^2.2.0",
"mock-fs": "^5.1.1",
- "nodemon": "^2.0.2",
+ "nodemon": "^3.0.1",
"ts-node": "^10.0.0"
},
"nodemonConfig": {
diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs
index 194549f079..08d2abff59 100644
--- a/packages/create-app/templates/default-app/app-config.yaml.hbs
+++ b/packages/create-app/templates/default-app/app-config.yaml.hbs
@@ -46,9 +46,10 @@ integrations:
proxy:
### Example for how to add a proxy endpoint for the frontend.
### A typical reason to do this is to handle HTTPS and CORS for internal services.
- # '/test':
- # target: 'https://example.com'
- # changeOrigin: true
+ # endpoints:
+ # '/test':
+ # target: 'https://example.com'
+ # changeOrigin: true
# Reference documentation http://backstage.io/docs/features/techdocs/configuration
# Note: After experimenting with basic setup, use CI/CD to generate docs
diff --git a/packages/create-app/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs
index 0682114a35..c53ed6970f 100644
--- a/packages/create-app/templates/default-app/package.json.hbs
+++ b/packages/create-app/templates/default-app/package.json.hbs
@@ -31,7 +31,7 @@
"devDependencies": {
"@backstage/cli": "^{{version '@backstage/cli'}}",
"@spotify/prettier-config": "^12.0.0",
- "concurrently": "^6.0.0",
+ "concurrently": "^8.0.0",
"lerna": "^4.0.0",
"node-gyp": "^9.0.0",
"prettier": "^2.3.2",
diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md
index 45df686876..99fb4f1372 100644
--- a/packages/dev-utils/CHANGELOG.md
+++ b/packages/dev-utils/CHANGELOG.md
@@ -1,5 +1,44 @@
# @backstage/dev-utils
+## 1.0.18
+
+### Patch Changes
+
+- 254ad469f053: Removed deprecated calls to `app.getProvider()` and `app.getRouter()` in `DevAppBuilder`
+- Updated dependencies
+ - @backstage/core-app-api@1.9.1
+ - @backstage/integration-react@1.1.16
+ - @backstage/core-components@0.13.4
+ - @backstage/plugin-catalog-react@1.8.1
+ - @backstage/app-defaults@1.4.2
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/test-utils@1.4.2
+ - @backstage/catalog-model@1.4.1
+ - @backstage/theme@0.4.1
+
+## 1.0.18-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.8.1-next.1
+ - @backstage/integration-react@1.1.16-next.1
+
+## 1.0.18-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration-react@1.1.16-next.1
+ - @backstage/app-defaults@1.4.2-next.0
+ - @backstage/catalog-model@1.4.1
+ - @backstage/core-app-api@1.9.1-next.0
+ - @backstage/core-components@0.13.4-next.0
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/test-utils@1.4.2-next.0
+ - @backstage/theme@0.4.1
+ - @backstage/plugin-catalog-react@1.8.1-next.0
+
## 1.0.18-next.0
### Patch Changes
diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json
index d7e12209c3..44b16b4a62 100644
--- a/packages/dev-utils/package.json
+++ b/packages/dev-utils/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/dev-utils",
"description": "Utilities for developing Backstage plugins.",
- "version": "1.0.18-next.0",
+ "version": "1.0.18",
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx
index 36e87855ed..5f2b21b8d2 100644
--- a/packages/dev-utils/src/devApp/render.tsx
+++ b/packages/dev-utils/src/devApp/render.tsx
@@ -15,7 +15,7 @@
*/
import { createApp } from '@backstage/app-defaults';
-import { FlatRoutes } from '@backstage/core-app-api';
+import { AppRouter, FlatRoutes } from '@backstage/core-app-api';
import {
AlertDisplay,
OAuthRequestDialog,
@@ -195,35 +195,30 @@ export class DevAppBuilder {
},
});
- const AppProvider = app.getProvider();
- const AppRouter = app.getRouter();
+ const DevApp = (
+ <>
+
+
+ {this.rootChildren}
+
+
+
+
+ {this.sidebarItems}
+
+
+
+
+
+ {this.routes}
+ } />
+
+
+
+ >
+ );
- const DevApp = () => {
- return (
-
-
-
- {this.rootChildren}
-
-
-
-
- {this.sidebarItems}
-
-
-
-
-
- {this.routes}
- } />
-
-
-
-
- );
- };
-
- return DevApp;
+ return app.createRoot(DevApp);
}
/**
diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md
index f6f29ff3f2..6bf5527c4b 100644
--- a/packages/e2e-test/CHANGELOG.md
+++ b/packages/e2e-test/CHANGELOG.md
@@ -1,5 +1,30 @@
# e2e-test
+## 0.2.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.5.4
+ - @backstage/cli-common@0.1.12
+ - @backstage/errors@1.2.1
+
+## 0.2.6-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.5.4-next.2
+
+## 0.2.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.5.4-next.1
+ - @backstage/cli-common@0.1.12
+ - @backstage/errors@1.2.1
+
## 0.2.6-next.0
### Patch Changes
diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json
index 48ef90db6f..48d4643394 100644
--- a/packages/e2e-test/package.json
+++ b/packages/e2e-test/package.json
@@ -1,7 +1,7 @@
{
"name": "e2e-test",
"description": "E2E test for verifying Backstage packages",
- "version": "0.2.6-next.0",
+ "version": "0.2.6",
"private": true,
"backstage": {
"role": "cli"
@@ -35,7 +35,7 @@
"cross-fetch": "^3.1.5",
"fs-extra": "10.1.0",
"handlebars": "^4.7.3",
- "pgtools": "^0.3.0",
+ "pgtools": "^1.0.0",
"puppeteer": "^17.0.0",
"tree-kill": "^1.2.2"
},
@@ -44,7 +44,7 @@
"@types/fs-extra": "^9.0.1",
"@types/node": "^16.11.26",
"@types/puppeteer": "^5.4.4",
- "nodemon": "^2.0.2",
+ "nodemon": "^3.0.1",
"ts-node": "^10.0.0"
},
"nodemonConfig": {
diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md
index 4f275a8750..9d08c09bd9 100644
--- a/packages/integration-react/CHANGELOG.md
+++ b/packages/integration-react/CHANGELOG.md
@@ -1,5 +1,29 @@
# @backstage/integration-react
+## 1.1.16
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/integration@1.6.0
+ - @backstage/core-components@0.13.4
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/config@1.0.8
+ - @backstage/theme@0.4.1
+
+## 1.1.16-next.1
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/integration@1.5.1
+ - @backstage/config@1.0.8
+ - @backstage/core-components@0.13.4-next.0
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/theme@0.4.1
+
## 1.1.16-next.0
### Patch Changes
diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json
index 616826b430..ec68a20f36 100644
--- a/packages/integration-react/package.json
+++ b/packages/integration-react/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/integration-react",
"description": "Frontend package for managing integrations towards external systems",
- "version": "1.1.16-next.0",
+ "version": "1.1.16",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -13,6 +13,12 @@
"backstage": {
"role": "web-library"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "packages/integration-react"
+ },
"scripts": {
"build": "backstage-cli package build",
"start": "backstage-cli package start",
diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md
index d1f3f46306..b4b97f579e 100644
--- a/packages/integration/CHANGELOG.md
+++ b/packages/integration/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/integration
+## 1.6.0
+
+### Minor Changes
+
+- 443afcf7f567: Added `buildGerritGitilesArchiveUrl()` to construct a Gitiles URL to download an archive.
+ Gitiles URL that uses an authenticated prefix (`/a/`) can now be parsed by the integration.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+
## 1.5.1
### Patch Changes
diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md
index c2501b4ac2..7266950ac4 100644
--- a/packages/integration/api-report.md
+++ b/packages/integration/api-report.md
@@ -172,6 +172,14 @@ export type BitbucketServerIntegrationConfig = {
password?: string;
};
+// @public
+export function buildGerritGitilesArchiveUrl(
+ config: GerritIntegrationConfig,
+ project: string,
+ branch: string,
+ filePath: string,
+): string;
+
// @public
export class DefaultGithubCredentialsProvider
implements GithubCredentialsProvider
diff --git a/packages/integration/package.json b/packages/integration/package.json
index e02e070266..b0da382abd 100644
--- a/packages/integration/package.json
+++ b/packages/integration/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/integration",
"description": "Helpers for managing integrations towards external systems",
- "version": "1.5.1",
+ "version": "1.6.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/packages/integration/src/gerrit/core.test.ts b/packages/integration/src/gerrit/core.test.ts
index 5a829e88f9..c00bae384f 100644
--- a/packages/integration/src/gerrit/core.test.ts
+++ b/packages/integration/src/gerrit/core.test.ts
@@ -20,6 +20,7 @@ import fetch from 'cross-fetch';
import { setupRequestMockHandlers } from '../helpers';
import { GerritIntegrationConfig } from './config';
import {
+ buildGerritGitilesArchiveUrl,
buildGerritGitilesUrl,
getGerritBranchApiUrl,
getGerritCloneRepoUrl,
@@ -33,6 +34,41 @@ describe('gerrit core', () => {
const worker = setupServer();
setupRequestMockHandlers(worker);
+ describe('buildGerritGitilesArchiveUrl', () => {
+ const config: GerritIntegrationConfig = {
+ host: 'gerrit.com',
+ gitilesBaseUrl: 'https://gerrit.com/gitiles',
+ };
+ it('can create an archive url for a branch', () => {
+ expect(buildGerritGitilesArchiveUrl(config, 'repo', 'dev', '')).toEqual(
+ 'https://gerrit.com/gitiles/repo/+archive/refs/heads/dev.tar.gz',
+ );
+
+ expect(buildGerritGitilesArchiveUrl(config, 'repo', 'dev', '/')).toEqual(
+ 'https://gerrit.com/gitiles/repo/+archive/refs/heads/dev.tar.gz',
+ );
+ });
+ it('can create an archive url for a specific directory', () => {
+ expect(
+ buildGerritGitilesArchiveUrl(config, 'repo', 'dev', 'docs'),
+ ).toEqual(
+ 'https://gerrit.com/gitiles/repo/+archive/refs/heads/dev/docs.tar.gz',
+ );
+ });
+ it('can create an authenticated url when auth is enabled', () => {
+ const authConfig = {
+ ...config,
+ username: 'username',
+ password: 'password',
+ };
+ expect(
+ buildGerritGitilesArchiveUrl(authConfig, 'repo', 'dev', 'docs'),
+ ).toEqual(
+ 'https://gerrit.com/a/gitiles/repo/+archive/refs/heads/dev/docs.tar.gz',
+ );
+ });
+ });
+
describe('buildGerritGitilesUrl', () => {
it('can create an url from arguments', () => {
const config: GerritIntegrationConfig = {
@@ -86,6 +122,25 @@ describe('gerrit core', () => {
);
expect(rootPath).toEqual('/');
});
+ it('can parse a valid authenticated gitiles url.', () => {
+ const config: GerritIntegrationConfig = {
+ host: 'gerrit.com',
+ gitilesBaseUrl: 'https://gerrit.com/gitiles',
+ };
+ const { branch, filePath, project } = parseGerritGitilesUrl(
+ config,
+ 'https://gerrit.com/a/gitiles/web/project/+/refs/heads/master/README.md',
+ );
+ expect(project).toEqual('web/project');
+ expect(branch).toEqual('master');
+ expect(filePath).toEqual('README.md');
+
+ const { filePath: rootPath } = parseGerritGitilesUrl(
+ config,
+ 'https://gerrit.com/gitiles/web/project/+/refs/heads/master',
+ );
+ expect(rootPath).toEqual('/');
+ });
it('throws on incorrect gitiles urls.', () => {
const config: GerritIntegrationConfig = {
host: 'gerrit.com',
diff --git a/packages/integration/src/gerrit/core.ts b/packages/integration/src/gerrit/core.ts
index 0403d8fc75..107d5a4469 100644
--- a/packages/integration/src/gerrit/core.ts
+++ b/packages/integration/src/gerrit/core.ts
@@ -37,6 +37,7 @@ const GERRIT_BODY_PREFIX = ")]}'";
*
* Gitiles url:
* https://g.com/optional_path/\{project\}/+/refs/heads/\{branch\}/\{filePath\}
+ * https://g.com/a/optional_path/\{project\}/+/refs/heads/\{branch\}/\{filePath\}
*
*
* @param url - An URL pointing to a file stored in git.
@@ -47,7 +48,17 @@ export function parseGerritGitilesUrl(
config: GerritIntegrationConfig,
url: string,
): { branch: string; filePath: string; project: string } {
- const urlPath = url.replace(config.gitilesBaseUrl!, '');
+ const baseUrlParse = new URL(config.gitilesBaseUrl!);
+ const urlParse = new URL(url);
+
+ // Remove the gerrit authentication prefix '/a/' from the url
+ // In case of the gitilesBaseUrl is https://review.gerrit.com/plugins/gitiles
+ // and the url provided is https://review.gerrit.com/a/plugins/gitiles/...
+ // remove the prefix only if the pathname start with '/a/'
+ const urlPath = urlParse.pathname
+ .substring(urlParse.pathname.startsWith('/a/') ? 2 : 0)
+ .replace(baseUrlParse.pathname, '');
+
const parts = urlPath.split('/').filter(p => !!p);
const projectEndIndex = parts.indexOf('+');
@@ -91,6 +102,28 @@ export function buildGerritGitilesUrl(
}/${project}/+/refs/heads/${branch}/${trimStart(filePath, '/')}`;
}
+/**
+ * Build a Gerrit Gitiles archive url that targets a specific branch and path
+ *
+ * @param config - A Gerrit provider config.
+ * @param project - The name of the git project
+ * @param branch - The branch we will target.
+ * @param filePath - The absolute file path.
+ * @public
+ */
+export function buildGerritGitilesArchiveUrl(
+ config: GerritIntegrationConfig,
+ project: string,
+ branch: string,
+ filePath: string,
+): string {
+ const archiveName =
+ filePath === '/' || filePath === '' ? '.tar.gz' : `/${filePath}.tar.gz`;
+ return `${getGitilesAuthenticationUrl(
+ config,
+ )}/${project}/+archive/refs/heads/${branch}${archiveName}`;
+}
+
/**
* Return the authentication prefix.
*
@@ -109,6 +142,26 @@ export function getAuthenticationPrefix(
return config.password ? '/a/' : '/';
}
+/**
+ * Return the authentication gitiles url.
+ *
+ * @remarks
+ *
+ * To authenticate with a password the API url must be prefixed with "/a/".
+ * If no password is set anonymous access (without the prefix) will
+ * be used.
+ *
+ * @param config - A Gerrit provider config.
+ */
+export function getGitilesAuthenticationUrl(
+ config: GerritIntegrationConfig,
+): string {
+ const parsedUrl = new URL(config.gitilesBaseUrl!);
+ return `${parsedUrl.protocol}//${parsedUrl.host}${getAuthenticationPrefix(
+ config,
+ )}${parsedUrl.pathname.substring(1)}`;
+}
+
/**
* Return the url to get branch info from the Gerrit API.
*
diff --git a/packages/integration/src/gerrit/index.ts b/packages/integration/src/gerrit/index.ts
index 127c949c4e..6c6295f7a3 100644
--- a/packages/integration/src/gerrit/index.ts
+++ b/packages/integration/src/gerrit/index.ts
@@ -19,6 +19,7 @@ export {
readGerritIntegrationConfigs,
} from './config';
export {
+ buildGerritGitilesArchiveUrl,
getGerritBranchApiUrl,
getGerritCloneRepoUrl,
getGerritFileContentsApiUrl,
diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md
index b8f37f4977..ddebca0746 100644
--- a/packages/repo-tools/CHANGELOG.md
+++ b/packages/repo-tools/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/repo-tools
+## 0.3.3
+
+### Patch Changes
+
+- 75702e85862a: Bumped `@microsoft/api-extractor` dependency to `^7.36.4`, and `@microsoft/api-documenter` to `^7.22.33`.
+- 1f3337ebc707: Introducing a new, experimental command `backstage-repo-tools generate-catalog-info`, which can be used to create standardized `catalog-info.yaml` files for each Backstage package in a Backstage monorepo. It can also be used to automatically fix existing `catalog-info.yaml` files with the correct metadata (including `metadata.name`, `metadata.title`, and `metadata.description` introspected from the package's `package.json`, as well as `spec.owner` introspected from `CODEOWNERS`), e.g. in a post-commit hook.
+- ebeb77586975: Update `schema openapi generate` command to now create a default router that can be imported and used directly.
+- Updated dependencies
+ - @backstage/cli-node@0.1.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/cli-common@0.1.12
+ - @backstage/errors@1.2.1
+
+## 0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-node@0.1.3-next.0
+ - @backstage/cli-common@0.1.12
+ - @backstage/errors@1.2.1
+
## 0.3.3-next.0
### Patch Changes
diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md
index a0df5e721c..9c87bd8dff 100644
--- a/packages/repo-tools/cli-report.md
+++ b/packages/repo-tools/cli-report.md
@@ -14,6 +14,7 @@ Options:
Commands:
api-reports [options] [paths...]
type-deps
+ generate-catalog-info [options]
schema [command]
help [command]
```
@@ -36,6 +37,16 @@ Options:
-h, --help
```
+### `backstage-repo-tools generate-catalog-info`
+
+```
+Usage: backstage-repo-tools generate-catalog-info [options]
+
+Options:
+ --dry-run
+ -h, --help
+```
+
### `backstage-repo-tools schema`
```
diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json
index c05cd30c75..82099ea621 100644
--- a/packages/repo-tools/package.json
+++ b/packages/repo-tools/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/repo-tools",
"description": "CLI for Backstage repo tooling ",
- "version": "0.3.3-next.0",
+ "version": "0.3.3",
"publishConfig": {
"access": "public"
},
@@ -32,12 +32,13 @@
"dependencies": {
"@apidevtools/swagger-parser": "^10.1.0",
"@apisyouwonthate/style-guide": "^1.4.0",
+ "@backstage/catalog-model": "workspace:^",
"@backstage/cli-common": "workspace:^",
"@backstage/cli-node": "workspace:^",
"@backstage/errors": "workspace:^",
"@manypkg/get-packages": "^1.1.3",
- "@microsoft/api-documenter": "^7.19.27",
- "@microsoft/api-extractor": "^7.33.7",
+ "@microsoft/api-documenter": "^7.22.33",
+ "@microsoft/api-extractor": "^7.36.4",
"@stoplight/spectral-core": "^1.18.0",
"@stoplight/spectral-formatters": "^1.1.0",
"@stoplight/spectral-functions": "^1.7.2",
@@ -46,6 +47,7 @@
"@stoplight/spectral-runtime": "^1.1.2",
"@stoplight/types": "^13.14.0",
"chalk": "^4.0.0",
+ "codeowners-utils": "^1.0.2",
"commander": "^9.1.0",
"fs-extra": "10.1.0",
"glob": "^8.0.3",
@@ -54,7 +56,8 @@
"lodash": "^4.17.21",
"minimatch": "^5.1.1",
"p-limit": "^3.0.2",
- "ts-node": "^10.0.0"
+ "ts-node": "^10.0.0",
+ "yaml-diff-patch": "^2.0.0"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
diff --git a/packages/repo-tools/src/commands/generate-catalog-info/codeowners.ts b/packages/repo-tools/src/commands/generate-catalog-info/codeowners.ts
new file mode 100644
index 0000000000..f485e872e9
--- /dev/null
+++ b/packages/repo-tools/src/commands/generate-catalog-info/codeowners.ts
@@ -0,0 +1,69 @@
+/*
+ * 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 {
+ CodeOwnersEntry,
+ CODEOWNERS_PATHS,
+ matchFile as matchCodeowner,
+ parse as parseCodeowners,
+} from 'codeowners-utils';
+import { relative as relativePath, resolve as resolvePath } from 'path';
+import { readFile } from './utils';
+
+export async function loadCodeowners(): Promise {
+ const maybeFiles = await Promise.allSettled(
+ CODEOWNERS_PATHS.map(async path =>
+ readFile(resolvePath('.', path), { encoding: 'utf-8' }),
+ ),
+ );
+ const file = maybeFiles.find(
+ maybeFile => maybeFile.status === 'fulfilled',
+ ) as PromiseFulfilledResult | undefined;
+
+ if (!file) {
+ throw new Error(
+ 'This utility expects a CODEOWNERS file, but no such file was found.',
+ );
+ }
+
+ return parseCodeowners(file.value);
+}
+
+export function getPossibleCodeowners(
+ codeowners: CodeOwnersEntry[],
+ relPath: string,
+): string[] {
+ const codeownerMaybe = matchCodeowner(relPath, codeowners);
+ return codeownerMaybe
+ ? codeownerMaybe.owners.map(
+ owner => (owner.match(/(?:\@[^\/]+\/)?([^\@\/]*)$/) || [])[1],
+ )
+ : [];
+}
+
+export function getOwnerFromCodeowners(
+ codeowners: CodeOwnersEntry[],
+ absPath: string,
+): string {
+ const relPath = relativePath('.', absPath);
+ const possibleOwners = getPossibleCodeowners(codeowners, relPath);
+ const owner = possibleOwners.slice(-1)[0];
+
+ if (!owner) {
+ throw new Error(`${relPath} isn't owned by anyone in CODEOWNERS`);
+ }
+
+ return owner;
+}
diff --git a/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts b/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts
new file mode 100644
index 0000000000..08f73898c8
--- /dev/null
+++ b/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts
@@ -0,0 +1,200 @@
+/*
+ * 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 YAML from 'js-yaml';
+import pLimit from 'p-limit';
+import { relative as relativePath, resolve as resolvePath } from 'path';
+import { yamlOverwrite } from 'yaml-diff-patch';
+import chalk from 'chalk';
+import { PackageGraph, PackageRole } from '@backstage/cli-node';
+import { Entity } from '@backstage/catalog-model';
+import {
+ getOwnerFromCodeowners,
+ getPossibleCodeowners,
+ loadCodeowners,
+} from './codeowners';
+import {
+ BackstagePackageJson,
+ isBackstagePackage,
+ readFile,
+ writeFile,
+} from './utils';
+import { CodeOwnersEntry } from 'codeowners-utils';
+
+type CreateFixPackageInfoYamlsOptions = {
+ dryRun?: boolean;
+};
+
+export default async (opts: CreateFixPackageInfoYamlsOptions) => {
+ const { dryRun = false } = opts;
+ const packages = await PackageGraph.listTargetPackages();
+ const codeowners = await loadCodeowners();
+ const limit = pLimit(10);
+
+ const results = await Promise.allSettled(
+ packages.map(({ packageJson, dir }) =>
+ limit(async () => {
+ if (!isBackstagePackage(packageJson)) {
+ return;
+ }
+
+ // Check if there is already a corresponding catalog-info.yaml
+ const infoYamlPath = resolvePath(dir, 'catalog-info.yaml');
+ let yamlString = '';
+
+ try {
+ yamlString = await readFile(infoYamlPath, { encoding: 'utf-8' });
+ } catch (e) {
+ if (e.code === 'ENOENT') {
+ await createCatalogInfoYaml({
+ yamlPath: infoYamlPath,
+ packageJson,
+ codeowners,
+ dryRun,
+ });
+ return;
+ }
+
+ throw e;
+ }
+
+ await fixCatalogInfoYaml({
+ yamlPath: infoYamlPath,
+ packageJson,
+ codeowners,
+ yamlString,
+ dryRun,
+ });
+ }),
+ ),
+ );
+
+ const rejects = results.filter(
+ r => r.status === 'rejected',
+ ) as PromiseRejectedResult[];
+ if (rejects.length > 0) {
+ // Problems encountered. Print details here.
+ console.error(
+ chalk.red('Unable to create or fix catalog-info.yaml files\n'),
+ );
+ rejects.forEach(reject => console.error(` ${reject.reason}`));
+ console.error();
+ process.exit(1);
+ }
+};
+
+type CreateOptions = {
+ yamlPath: string;
+ packageJson: BackstagePackageJson;
+ codeowners: CodeOwnersEntry[];
+ dryRun: boolean;
+};
+
+type FixOptions = CreateOptions & {
+ yamlString: string;
+};
+
+type BackstagePackageEntity = Entity & {
+ spec: {
+ type: 'backstage-package';
+ backstageRole: PackageRole;
+ lifecycle: string;
+ owner: string;
+ [key: string]: any;
+ };
+};
+
+function createCatalogInfoYaml(options: CreateOptions) {
+ const { codeowners, dryRun, packageJson, yamlPath } = options;
+ const owner = getOwnerFromCodeowners(codeowners, yamlPath);
+ const entity = createOrMergeEntity(packageJson, owner);
+
+ return dryRun
+ ? Promise.resolve(console.error(`Create ${relativePath('.', yamlPath)}`))
+ : writeFile(yamlPath, YAML.dump(entity));
+}
+
+function fixCatalogInfoYaml(options: FixOptions) {
+ const { codeowners, dryRun, packageJson, yamlPath, yamlString } = options;
+ const possibleOwners = getPossibleCodeowners(
+ codeowners,
+ relativePath('.', yamlPath),
+ );
+ const safeName = packageJson.name
+ .replace(/[^a-z0-9_\-\.]+/g, '-')
+ .replace(/^[^a-z0-9]|[^a-z0-9]$/g, '');
+ let yamlJson: BackstagePackageEntity;
+
+ try {
+ yamlJson = YAML.load(yamlString) as BackstagePackageEntity;
+ } catch (e) {
+ throw new Error(`Unable to parse ${relativePath('.', yamlPath)}: ${e}`);
+ }
+
+ const badOwner = !possibleOwners.includes(yamlJson.spec?.owner);
+ const badTitle = yamlJson.metadata.title !== packageJson.name;
+ const badName = yamlJson.metadata.name !== safeName;
+ const badType =
+ yamlJson.spec?.type !== `backstage-${packageJson.backstage.role}`;
+ const badDesc = yamlJson.metadata.description !== packageJson.description;
+
+ if (badOwner || badTitle || badName || badType || badDesc) {
+ const owner = badOwner
+ ? getOwnerFromCodeowners(codeowners, yamlPath)
+ : yamlJson.spec?.owner;
+ const newJson = createOrMergeEntity(packageJson, owner, yamlJson);
+ return dryRun
+ ? Promise.resolve(console.error(`Update ${relativePath('.', yamlPath)}`))
+ : writeFile(yamlPath, yamlOverwrite(yamlString, newJson));
+ }
+
+ return Promise.resolve();
+}
+
+/**
+ * Canonical representation on how to create (or update) a backstage-package
+ * component.
+ */
+function createOrMergeEntity(
+ packageJson: BackstagePackageJson,
+ owner: string,
+ existingEntity: BackstagePackageEntity | Record = {},
+): BackstagePackageEntity {
+ const safeEntityName = packageJson.name
+ .replace(/[^a-z0-9_\-\.]+/g, '-')
+ .replace(/^[^a-z0-9]|[^a-z0-9]$/g, '');
+
+ return {
+ ...existingEntity,
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ ...existingEntity.metadata,
+ // Provide default name/title/description values.
+ name: safeEntityName,
+ title: packageJson.name,
+ ...(packageJson.description
+ ? { description: packageJson.description }
+ : undefined),
+ },
+ spec: {
+ lifecycle: 'experimental',
+ ...existingEntity.spec,
+ type: `backstage-${packageJson.backstage.role}`,
+ owner,
+ },
+ };
+}
diff --git a/packages/repo-tools/src/commands/generate-catalog-info/utils.ts b/packages/repo-tools/src/commands/generate-catalog-info/utils.ts
new file mode 100644
index 0000000000..f446f4e867
--- /dev/null
+++ b/packages/repo-tools/src/commands/generate-catalog-info/utils.ts
@@ -0,0 +1,42 @@
+/*
+ * 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 fs from 'fs';
+import { Package } from '@manypkg/get-packages';
+import {
+ BackstagePackageJson as BackstagePackageJsonActual,
+ PackageRole,
+} from '@backstage/cli-node';
+import { promisify } from 'util';
+
+export const readFile = promisify(fs.readFile);
+export const writeFile = promisify(fs.writeFile);
+
+export type BackstagePackageJson = BackstagePackageJsonActual & {
+ description?: string;
+ backstage: {
+ role: PackageRole;
+ };
+};
+
+export function isBackstagePackage(
+ packageJson: Package['packageJson'],
+): packageJson is BackstagePackageJson {
+ return (
+ packageJson &&
+ packageJson.hasOwnProperty('backstage') &&
+ (packageJson as any)?.backstage?.role !== 'undefined'
+ );
+}
diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts
index e19ffde4b8..7aa574d291 100644
--- a/packages/repo-tools/src/commands/index.ts
+++ b/packages/repo-tools/src/commands/index.ts
@@ -96,6 +96,21 @@ export function registerCommands(program: Command) {
.description('Find inconsistencies in types of all packages and plugins')
.action(lazy(() => import('./type-deps/type-deps').then(m => m.default)));
+ program
+ .command('generate-catalog-info')
+ .option(
+ '--dry-run',
+ 'Shows what would happen without actually writing any yaml.',
+ )
+ .description('Create or fix info yaml files for all backstage packages')
+ .action(
+ lazy(() =>
+ import('./generate-catalog-info/generate-catalog-info').then(
+ m => m.default,
+ ),
+ ),
+ );
+
registerSchemaCommand(program);
}
diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md
index 26f2246d13..6728d9de41 100644
--- a/packages/techdocs-cli-embedded-app/CHANGELOG.md
+++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md
@@ -1,5 +1,53 @@
# techdocs-cli-embedded-app
+## 0.2.85
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.9.1
+ - @backstage/integration-react@1.1.16
+ - @backstage/cli@0.22.10
+ - @backstage/core-components@0.13.4
+ - @backstage/plugin-catalog@1.12.1
+ - @backstage/app-defaults@1.4.2
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/test-utils@1.4.2
+ - @backstage/plugin-techdocs@1.6.6
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/theme@0.4.1
+ - @backstage/plugin-techdocs-react@1.1.9
+
+## 0.2.85-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog@1.12.1-next.2
+ - @backstage/plugin-techdocs@1.6.6-next.2
+ - @backstage/cli@0.22.10-next.1
+ - @backstage/integration-react@1.1.16-next.1
+
+## 0.2.85-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration-react@1.1.16-next.1
+ - @backstage/cli@0.22.10-next.1
+ - @backstage/plugin-catalog@1.12.1-next.1
+ - @backstage/plugin-techdocs@1.6.6-next.1
+ - @backstage/app-defaults@1.4.2-next.0
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/core-app-api@1.9.1-next.0
+ - @backstage/core-components@0.13.4-next.0
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/test-utils@1.4.2-next.0
+ - @backstage/theme@0.4.1
+ - @backstage/plugin-techdocs-react@1.1.9-next.0
+
## 0.2.85-next.0
### Patch Changes
diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json
index 3bbef65e68..c8097fca3b 100644
--- a/packages/techdocs-cli-embedded-app/package.json
+++ b/packages/techdocs-cli-embedded-app/package.json
@@ -1,11 +1,17 @@
{
"name": "techdocs-cli-embedded-app",
- "version": "0.2.85-next.0",
+ "version": "0.2.85",
"private": true,
"backstage": {
"role": "frontend"
},
"bundled": true,
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "packages/techdocs-cli-embedded-app"
+ },
"dependencies": {
"@backstage/app-defaults": "workspace:^",
"@backstage/catalog-model": "workspace:^",
diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md
index daefe75e38..8334aa54b6 100644
--- a/packages/techdocs-cli/CHANGELOG.md
+++ b/packages/techdocs-cli/CHANGELOG.md
@@ -1,5 +1,36 @@
# @techdocs/cli
+## 1.4.5
+
+### Patch Changes
+
+- 971bdd6a4732: Bumped internal `nodemon` dependency.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/plugin-techdocs-node@1.7.4
+ - @backstage/catalog-model@1.4.1
+ - @backstage/cli-common@0.1.12
+ - @backstage/config@1.0.8
+
+## 1.4.5-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-techdocs-node@1.7.4-next.2
+
+## 1.4.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-techdocs-node@1.7.4-next.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/cli-common@0.1.12
+ - @backstage/config@1.0.8
+
## 1.4.5-next.0
### Patch Changes
diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json
index d59450992b..3dc8eda540 100644
--- a/packages/techdocs-cli/package.json
+++ b/packages/techdocs-cli/package.json
@@ -1,7 +1,7 @@
{
"name": "@techdocs/cli",
"description": "Utility CLI for managing TechDocs sites in Backstage.",
- "version": "1.4.5-next.0",
+ "version": "1.4.5",
"publishConfig": {
"access": "public"
},
@@ -46,7 +46,7 @@
"@types/webpack-env": "^1.15.3",
"cypress": "^10.0.0",
"find-process": "^1.4.5",
- "nodemon": "^2.0.2",
+ "nodemon": "^3.0.1",
"techdocs-cli-embedded-app": "link:../techdocs-cli-embedded-app",
"ts-node": "^10.0.0"
},
diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md
index a3fb8ba382..d3e28b30d2 100644
--- a/packages/test-utils/CHANGELOG.md
+++ b/packages/test-utils/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/test-utils
+## 1.4.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.9.1
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/config@1.0.8
+ - @backstage/theme@0.4.1
+ - @backstage/types@1.1.0
+ - @backstage/plugin-permission-common@0.7.7
+ - @backstage/plugin-permission-react@0.4.14
+
## 1.4.2-next.0
### Patch Changes
diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json
index ef074d13b2..73ef2c5268 100644
--- a/packages/test-utils/package.json
+++ b/packages/test-utils/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/test-utils",
"description": "Utilities to test Backstage plugins and apps.",
- "version": "1.4.2-next.0",
+ "version": "1.4.2",
"publishConfig": {
"access": "public"
},
diff --git a/plugins/adr-backend/CHANGELOG.md b/plugins/adr-backend/CHANGELOG.md
index c63b8bd687..e038706836 100644
--- a/plugins/adr-backend/CHANGELOG.md
+++ b/plugins/adr-backend/CHANGELOG.md
@@ -1,5 +1,43 @@
# @backstage/plugin-adr-backend
+## 0.3.6
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/integration@1.6.0
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/plugin-adr-common@0.2.12
+ - @backstage/plugin-search-common@1.2.5
+
+## 0.3.6-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-common@0.19.2-next.2
+
+## 0.3.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/integration@1.5.1
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/plugin-adr-common@0.2.11
+ - @backstage/plugin-search-common@1.2.5
+
## 0.3.6-next.0
### Patch Changes
diff --git a/plugins/adr-backend/package.json b/plugins/adr-backend/package.json
index e7f390b790..c15eafd6cf 100644
--- a/plugins/adr-backend/package.json
+++ b/plugins/adr-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-adr-backend",
- "version": "0.3.6-next.0",
+ "version": "0.3.6",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/adr-common/CHANGELOG.md b/plugins/adr-common/CHANGELOG.md
index 9b8d5dbf74..dfe54806fe 100644
--- a/plugins/adr-common/CHANGELOG.md
+++ b/plugins/adr-common/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-adr-common
+## 0.2.12
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.6.0
+ - @backstage/catalog-model@1.4.1
+ - @backstage/plugin-search-common@1.2.5
+
## 0.2.11
### Patch Changes
diff --git a/plugins/adr-common/package.json b/plugins/adr-common/package.json
index 2def65cf55..25e02845ef 100644
--- a/plugins/adr-common/package.json
+++ b/plugins/adr-common/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-adr-common",
"description": "Common functionalities for the adr plugin",
- "version": "0.2.11",
+ "version": "0.2.12",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/adr/CHANGELOG.md b/plugins/adr/CHANGELOG.md
index fbbf4c13de..e25c80d4d2 100644
--- a/plugins/adr/CHANGELOG.md
+++ b/plugins/adr/CHANGELOG.md
@@ -1,5 +1,45 @@
# @backstage/plugin-adr
+## 0.6.4
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/integration-react@1.1.16
+ - @backstage/core-components@0.13.4
+ - @backstage/plugin-catalog-react@1.8.1
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/plugin-search-react@1.6.4
+ - @backstage/catalog-model@1.4.1
+ - @backstage/theme@0.4.1
+ - @backstage/plugin-adr-common@0.2.12
+ - @backstage/plugin-search-common@1.2.5
+
+## 0.6.4-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.8.1-next.1
+ - @backstage/integration-react@1.1.16-next.1
+
+## 0.6.4-next.1
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/integration-react@1.1.16-next.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/core-components@0.13.4-next.0
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/theme@0.4.1
+ - @backstage/plugin-adr-common@0.2.11
+ - @backstage/plugin-catalog-react@1.8.1-next.0
+ - @backstage/plugin-search-common@1.2.5
+ - @backstage/plugin-search-react@1.6.4-next.0
+
## 0.6.4-next.0
### Patch Changes
diff --git a/plugins/adr/package.json b/plugins/adr/package.json
index 486ea800a2..37bf13619a 100644
--- a/plugins/adr/package.json
+++ b/plugins/adr/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-adr",
- "version": "0.6.4-next.0",
+ "version": "0.6.4",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -12,6 +12,12 @@
"backstage": {
"role": "frontend-plugin"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/adr"
+ },
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
diff --git a/plugins/airbrake-backend/CHANGELOG.md b/plugins/airbrake-backend/CHANGELOG.md
index c0f695f372..7eea9d8287 100644
--- a/plugins/airbrake-backend/CHANGELOG.md
+++ b/plugins/airbrake-backend/CHANGELOG.md
@@ -1,5 +1,35 @@
# @backstage/plugin-airbrake-backend
+## 0.2.21
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/config@1.0.8
+
+## 0.2.21-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-common@0.19.2-next.2
+
+## 0.2.21-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/config@1.0.8
+
## 0.2.21-next.0
### Patch Changes
diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json
index 06e5183a44..530de9a77a 100644
--- a/plugins/airbrake-backend/package.json
+++ b/plugins/airbrake-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-airbrake-backend",
- "version": "0.2.21-next.0",
+ "version": "0.2.21",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -12,6 +12,12 @@
"backstage": {
"role": "backend-plugin"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/airbrake-backend"
+ },
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
diff --git a/plugins/airbrake-backend/src/plugin.ts b/plugins/airbrake-backend/src/plugin.ts
index 79850c1272..90e790c05f 100644
--- a/plugins/airbrake-backend/src/plugin.ts
+++ b/plugins/airbrake-backend/src/plugin.ts
@@ -32,7 +32,7 @@ export const airbrakePlugin = createBackendPlugin({
register(env) {
env.registerInit({
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
logger: coreServices.logger,
httpRouter: coreServices.httpRouter,
},
diff --git a/plugins/airbrake/CHANGELOG.md b/plugins/airbrake/CHANGELOG.md
index e5b52f1932..b4d5afccdc 100644
--- a/plugins/airbrake/CHANGELOG.md
+++ b/plugins/airbrake/CHANGELOG.md
@@ -1,5 +1,41 @@
# @backstage/plugin-airbrake
+## 0.3.21
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/dev-utils@1.0.18
+ - @backstage/core-components@0.13.4
+ - @backstage/plugin-catalog-react@1.8.1
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/test-utils@1.4.2
+ - @backstage/catalog-model@1.4.1
+ - @backstage/theme@0.4.1
+
+## 0.3.21-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.8.1-next.1
+ - @backstage/dev-utils@1.0.18-next.2
+
+## 0.3.21-next.1
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/dev-utils@1.0.18-next.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/core-components@0.13.4-next.0
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/test-utils@1.4.2-next.0
+ - @backstage/theme@0.4.1
+ - @backstage/plugin-catalog-react@1.8.1-next.0
+
## 0.3.21-next.0
### Patch Changes
diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json
index 48a825f93b..5ccd8bd076 100644
--- a/plugins/airbrake/package.json
+++ b/plugins/airbrake/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-airbrake",
- "version": "0.3.21-next.0",
+ "version": "0.3.21",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -12,6 +12,12 @@
"backstage": {
"role": "frontend-plugin"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/airbrake"
+ },
"scripts": {
"build": "backstage-cli package build",
"start": "backstage-cli package start",
diff --git a/plugins/allure/CHANGELOG.md b/plugins/allure/CHANGELOG.md
index caa81f04cf..cd7c7973b5 100644
--- a/plugins/allure/CHANGELOG.md
+++ b/plugins/allure/CHANGELOG.md
@@ -1,5 +1,36 @@
# @backstage/plugin-allure
+## 0.1.37
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/core-components@0.13.4
+ - @backstage/plugin-catalog-react@1.8.1
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/theme@0.4.1
+
+## 0.1.37-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.8.1-next.1
+
+## 0.1.37-next.1
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/catalog-model@1.4.1
+ - @backstage/core-components@0.13.4-next.0
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/theme@0.4.1
+ - @backstage/plugin-catalog-react@1.8.1-next.0
+
## 0.1.37-next.0
### Patch Changes
diff --git a/plugins/allure/package.json b/plugins/allure/package.json
index f4809128ff..881d4d0c9d 100644
--- a/plugins/allure/package.json
+++ b/plugins/allure/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-allure",
"description": "A Backstage plugin that integrates with Allure",
- "version": "0.1.37-next.0",
+ "version": "0.1.37",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -13,6 +13,12 @@
"backstage": {
"role": "frontend-plugin"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/allure"
+ },
"scripts": {
"build": "backstage-cli package build",
"start": "backstage-cli package start",
diff --git a/plugins/analytics-module-ga/CHANGELOG.md b/plugins/analytics-module-ga/CHANGELOG.md
index d7c09cddb1..b6ceb1c28d 100644
--- a/plugins/analytics-module-ga/CHANGELOG.md
+++ b/plugins/analytics-module-ga/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/plugin-analytics-module-ga
+## 0.1.32
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/core-components@0.13.4
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/config@1.0.8
+ - @backstage/theme@0.4.1
+
+## 0.1.32-next.1
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/config@1.0.8
+ - @backstage/core-components@0.13.4-next.0
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/theme@0.4.1
+
## 0.1.32-next.0
### Patch Changes
diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json
index 302ee1cd86..434f231545 100644
--- a/plugins/analytics-module-ga/package.json
+++ b/plugins/analytics-module-ga/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-analytics-module-ga",
- "version": "0.1.32-next.0",
+ "version": "0.1.32",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -12,6 +12,12 @@
"backstage": {
"role": "frontend-plugin-module"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/analytics-module-ga"
+ },
"scripts": {
"build": "backstage-cli package build",
"start": "backstage-cli package start",
diff --git a/plugins/analytics-module-ga4/CHANGELOG.md b/plugins/analytics-module-ga4/CHANGELOG.md
index 9715015579..bc08d64809 100644
--- a/plugins/analytics-module-ga4/CHANGELOG.md
+++ b/plugins/analytics-module-ga4/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/plugin-analytics-module-ga4
+## 0.1.3
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/core-components@0.13.4
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/config@1.0.8
+ - @backstage/theme@0.4.1
+
+## 0.1.3-next.1
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/config@1.0.8
+ - @backstage/core-components@0.13.4-next.0
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/theme@0.4.1
+
## 0.1.3-next.0
### Patch Changes
diff --git a/plugins/analytics-module-ga4/package.json b/plugins/analytics-module-ga4/package.json
index 85f34468b1..63f03e440b 100644
--- a/plugins/analytics-module-ga4/package.json
+++ b/plugins/analytics-module-ga4/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-analytics-module-ga4",
- "version": "0.1.3-next.0",
+ "version": "0.1.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -12,6 +12,12 @@
"backstage": {
"role": "frontend-plugin-module"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/plugins/analytics-module-ga4"
+ },
"scripts": {
"build": "backstage-cli package build",
"start": "backstage-cli package start",
diff --git a/plugins/analytics-module-newrelic-browser/CHANGELOG.md b/plugins/analytics-module-newrelic-browser/CHANGELOG.md
index beea288700..3090cd3fc2 100644
--- a/plugins/analytics-module-newrelic-browser/CHANGELOG.md
+++ b/plugins/analytics-module-newrelic-browser/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-analytics-module-newrelic-browser
+## 0.0.1
+
+### Patch Changes
+
+- ec7357258853: Introduced the New Relic Browser analytics module. Check out the plugins [README.md](https://github.com/backstage/backstage/tree/master/plugins/analytics-module-newrelic-browser) for more details!
+- Updated dependencies
+ - @backstage/core-components@0.13.4
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/config@1.0.8
+
## 0.0.1-next.0
### Patch Changes
diff --git a/plugins/analytics-module-newrelic-browser/package.json b/plugins/analytics-module-newrelic-browser/package.json
index 677d3b8e16..ba72a1628b 100644
--- a/plugins/analytics-module-newrelic-browser/package.json
+++ b/plugins/analytics-module-newrelic-browser/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-analytics-module-newrelic-browser",
- "version": "0.0.1-next.0",
+ "version": "0.0.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/apache-airflow/CHANGELOG.md b/plugins/apache-airflow/CHANGELOG.md
index ef1d34dc23..e23c4a7c0c 100644
--- a/plugins/apache-airflow/CHANGELOG.md
+++ b/plugins/apache-airflow/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/plugin-apache-airflow
+## 0.2.14
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/core-components@0.13.4
+ - @backstage/core-plugin-api@1.5.3
+
+## 0.2.14-next.1
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/core-components@0.13.4-next.0
+ - @backstage/core-plugin-api@1.5.3
+
## 0.2.14-next.0
### Patch Changes
diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json
index b30abb3bc9..281d49c781 100644
--- a/plugins/apache-airflow/package.json
+++ b/plugins/apache-airflow/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-apache-airflow",
- "version": "0.2.14-next.0",
+ "version": "0.2.14",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -12,6 +12,12 @@
"backstage": {
"role": "frontend-plugin"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/apache-airflow"
+ },
"scripts": {
"build": "backstage-cli package build",
"start": "backstage-cli package start",
diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md
index 68923046f5..f84d57542b 100644
--- a/plugins/api-docs/CHANGELOG.md
+++ b/plugins/api-docs/CHANGELOG.md
@@ -1,5 +1,37 @@
# @backstage/plugin-api-docs
+## 0.9.7
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.13.4
+ - @backstage/plugin-catalog@1.12.1
+ - @backstage/plugin-catalog-react@1.8.1
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/theme@0.4.1
+
+## 0.9.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.8.1-next.1
+ - @backstage/plugin-catalog@1.12.1-next.2
+
+## 0.9.7-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog@1.12.1-next.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/core-components@0.13.4-next.0
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/theme@0.4.1
+ - @backstage/plugin-catalog-react@1.8.1-next.0
+
## 0.9.7-next.0
### Patch Changes
diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json
index 625fbd3138..1fe4133515 100644
--- a/plugins/api-docs/package.json
+++ b/plugins/api-docs/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-api-docs",
"description": "A Backstage plugin that helps represent API entities in the frontend",
- "version": "0.9.7-next.0",
+ "version": "0.9.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/apollo-explorer/CHANGELOG.md b/plugins/apollo-explorer/CHANGELOG.md
index 0051c8a9e7..0833267e83 100644
--- a/plugins/apollo-explorer/CHANGELOG.md
+++ b/plugins/apollo-explorer/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-apollo-explorer
+## 0.1.14
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/core-components@0.13.4
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/theme@0.4.1
+
+## 0.1.14-next.1
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/core-components@0.13.4-next.0
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/theme@0.4.1
+
## 0.1.14-next.0
### Patch Changes
diff --git a/plugins/apollo-explorer/package.json b/plugins/apollo-explorer/package.json
index 0b43fd63ba..aae09fe19a 100644
--- a/plugins/apollo-explorer/package.json
+++ b/plugins/apollo-explorer/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-apollo-explorer",
- "version": "0.1.14-next.0",
+ "version": "0.1.14",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -12,6 +12,12 @@
"backstage": {
"role": "frontend-plugin"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/apollo-explorer"
+ },
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md
index 7e048ba4d5..2ac0068ccc 100644
--- a/plugins/app-backend/CHANGELOG.md
+++ b/plugins/app-backend/CHANGELOG.md
@@ -1,5 +1,42 @@
# @backstage/plugin-app-backend
+## 0.3.48
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- d564ad142b17: Migrated the alpha `appBackend` export to use static configuration and extension points rather than accepting options.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/config-loader@1.4.0
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-app-node@0.1.0
+ - @backstage/config@1.0.8
+ - @backstage/types@1.1.0
+
+## 0.3.48-next.2
+
+### Patch Changes
+
+- d564ad142b17: Migrated the alpha `appBackend` export to use static configuration and extension points rather than accepting options.
+- Updated dependencies
+ - @backstage/plugin-app-node@0.1.0-next.0
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/config-loader@1.4.0-next.1
+
+## 0.3.48-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/config-loader@1.4.0-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/config@1.0.8
+ - @backstage/types@1.1.0
+
## 0.3.48-next.0
### Patch Changes
diff --git a/plugins/app-backend/alpha-api-report.md b/plugins/app-backend/alpha-api-report.md
index 20a2902557..5fb0546a79 100644
--- a/plugins/app-backend/alpha-api-report.md
+++ b/plugins/app-backend/alpha-api-report.md
@@ -4,18 +4,9 @@
```ts
import { BackendFeature } from '@backstage/backend-plugin-api';
-import express from 'express';
// @alpha
-export const appPlugin: (options: AppPluginOptions) => BackendFeature;
-
-// @alpha (undocumented)
-export type AppPluginOptions = {
- appPackageName?: string;
- staticFallbackHandler?: express.Handler;
- disableConfigInjection?: boolean;
- disableStaticFallbackCache?: boolean;
-};
+export const appPlugin: () => BackendFeature;
// (No @packageDocumentation comment for this package)
```
diff --git a/plugins/app-backend/config.d.ts b/plugins/app-backend/config.d.ts
new file mode 100644
index 0000000000..dc11bc4e0a
--- /dev/null
+++ b/plugins/app-backend/config.d.ts
@@ -0,0 +1,46 @@
+/*
+ * 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 interface Config {
+ app?: {
+ /**
+ * The name of the app package (in most Backstage repositories, this is the
+ * "name" field in `packages/app/package.json`) that content should be served
+ * from. The same app package should be added as a dependency to the backend
+ * package in order for it to be accessible at runtime.
+ *
+ * In a typical setup with a single app package, this will default to 'app'.
+ */
+ packageName?: string;
+
+ /**
+ * Disables the configuration injection. This can be useful if you're running in an environment
+ * with a read-only filesystem, or for some other reason don't want configuration to be injected.
+ *
+ * Note that this will cause the configuration used when building the app bundle to be used, unless
+ * a separate configuration loading strategy is set up.
+ *
+ * This also disables configuration injection though `APP_CONFIG_` environment variables.
+ */
+ disableConfigInjection?: boolean;
+
+ /**
+ * By default the app backend plugin will cache previously deployed static assets in the database.
+ * If you disable this, it is recommended to set a `staticFallbackHandler` instead.
+ */
+ disableStaticFallbackCache?: boolean;
+ };
+}
diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json
index 5574c8aba3..05db6bb37d 100644
--- a/plugins/app-backend/package.json
+++ b/plugins/app-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-app-backend",
"description": "A Backstage backend plugin that serves the Backstage frontend app",
- "version": "0.3.48-next.0",
+ "version": "0.3.48",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -49,6 +49,7 @@
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/config-loader": "workspace:^",
+ "@backstage/plugin-app-node": "workspace:^",
"@backstage/types": "workspace:^",
"@types/express": "^4.17.6",
"express": "^4.17.1",
@@ -73,8 +74,10 @@
"node-fetch": "^2.6.7",
"supertest": "^6.1.3"
},
+ "configSchema": "config.d.ts",
"files": [
"dist",
+ "config.d.ts",
"migrations/**/*.{js,d.ts}",
"static"
]
diff --git a/plugins/app-backend/src/alpha.ts b/plugins/app-backend/src/alpha.ts
index cc8276b853..edb5c8caf5 100644
--- a/plugins/app-backend/src/alpha.ts
+++ b/plugins/app-backend/src/alpha.ts
@@ -15,4 +15,3 @@
*/
export { appPlugin } from './service/appPlugin';
-export type { AppPluginOptions } from './service/appPlugin';
diff --git a/plugins/app-backend/src/service/appPlugin.test.ts b/plugins/app-backend/src/service/appPlugin.test.ts
index 518f56b7f9..b6a972b4c7 100644
--- a/plugins/app-backend/src/service/appPlugin.test.ts
+++ b/plugins/app-backend/src/service/appPlugin.test.ts
@@ -17,7 +17,7 @@
import mockFs from 'mock-fs';
import { resolve as resolvePath } from 'path';
import fetch from 'node-fetch';
-import { startTestBackend } from '@backstage/backend-test-utils';
+import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { appPlugin } from './appPlugin';
describe('appPlugin', () => {
@@ -40,9 +40,13 @@ describe('appPlugin', () => {
it('boots', async () => {
const { server } = await startTestBackend({
features: [
- appPlugin({
- appPackageName: 'app',
- disableStaticFallbackCache: true,
+ appPlugin(),
+ mockServices.rootConfig.factory({
+ data: {
+ app: {
+ disableStaticFallbackCache: true,
+ },
+ },
}),
],
});
diff --git a/plugins/app-backend/src/service/appPlugin.ts b/plugins/app-backend/src/service/appPlugin.ts
index a29f8fd5a8..9ba2ea6d6c 100644
--- a/plugins/app-backend/src/service/appPlugin.ts
+++ b/plugins/app-backend/src/service/appPlugin.ts
@@ -21,84 +21,49 @@ import {
} from '@backstage/backend-plugin-api';
import { createRouter } from './router';
import { loggerToWinstonLogger } from '@backstage/backend-common';
-
-/** @alpha */
-export type AppPluginOptions = {
- /**
- * The name of the app package (in most Backstage repositories, this is the
- * "name" field in `packages/app/package.json`) that content should be served
- * from. The same app package should be added as a dependency to the backend
- * package in order for it to be accessible at runtime.
- *
- * In a typical setup with a single app package, this will default to 'app'.
- */
- appPackageName?: string;
-
- /**
- * A request handler to handle requests for static content that are not present in the app bundle.
- *
- * This can be used to avoid issues with clients on older deployment versions trying to access lazy
- * loaded content that is no longer present. Typically the requests would fall back to a long-term
- * object store where all recently deployed versions of the app are present.
- *
- * Another option is to provide a `database` that will take care of storing the static assets instead.
- *
- * If both `database` and `staticFallbackHandler` are provided, the `database` will attempt to serve
- * static assets first, and if they are not found, the `staticFallbackHandler` will be called.
- */
- staticFallbackHandler?: express.Handler;
-
- /**
- * Disables the configuration injection. This can be useful if you're running in an environment
- * with a read-only filesystem, or for some other reason don't want configuration to be injected.
- *
- * Note that this will cause the configuration used when building the app bundle to be used, unless
- * a separate configuration loading strategy is set up.
- *
- * This also disables configuration injection though `APP_CONFIG_` environment variables.
- */
- disableConfigInjection?: boolean;
-
- /**
- * By default the app backend plugin will cache previously deployed static assets in the database.
- * If you disable this, it is recommended to set a `staticFallbackHandler` instead.
- */
- disableStaticFallbackCache?: boolean;
-};
+import { staticFallbackHandlerExtensionPoint } from '@backstage/plugin-app-node';
/**
* The App plugin is responsible for serving the frontend app bundle and static assets.
* @alpha
*/
-export const appPlugin = createBackendPlugin((options: AppPluginOptions) => ({
+export const appPlugin = createBackendPlugin({
pluginId: 'app',
register(env) {
+ let staticFallbackHandler: express.Handler | undefined;
+
+ env.registerExtensionPoint(staticFallbackHandlerExtensionPoint, {
+ setStaticFallbackHandler(handler) {
+ if (staticFallbackHandler) {
+ throw new Error(
+ 'Attempted to install a static fallback handler for the app-backend twice',
+ );
+ }
+ staticFallbackHandler = handler;
+ },
+ });
+
env.registerInit({
deps: {
logger: coreServices.logger,
- config: coreServices.config,
+ config: coreServices.rootConfig,
database: coreServices.database,
httpRouter: coreServices.httpRouter,
},
async init({ logger, config, database, httpRouter }) {
- const {
- appPackageName,
- staticFallbackHandler,
- disableConfigInjection,
- disableStaticFallbackCache,
- } = options;
+ const appPackageName =
+ config.getOptionalString('app.packageName') ?? 'app';
const winstonLogger = loggerToWinstonLogger(logger);
const router = await createRouter({
logger: winstonLogger,
config,
- database: disableStaticFallbackCache ? undefined : database,
- appPackageName: appPackageName ?? 'app',
+ database,
+ appPackageName,
staticFallbackHandler,
- disableConfigInjection,
});
httpRouter.use(router);
},
});
},
-}));
+});
diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts
index 4e94138439..40e291c19d 100644
--- a/plugins/app-backend/src/service/router.ts
+++ b/plugins/app-backend/src/service/router.ts
@@ -92,6 +92,13 @@ export async function createRouter(
): Promise {
const { config, logger, appPackageName, staticFallbackHandler } = options;
+ const disableConfigInjection =
+ options.disableConfigInjection ??
+ config.getOptionalBoolean('app.disableConfigInjection');
+ const disableStaticFallbackCache = config.getOptionalBoolean(
+ 'app.disableStaticFallbackCache',
+ );
+
const appDistDir = resolvePackagePath(appPackageName, 'dist');
const staticDir = resolvePath(appDistDir, 'static');
@@ -107,7 +114,7 @@ export async function createRouter(
logger.info(`Serving static app content from ${appDistDir}`);
- if (!options.disableConfigInjection) {
+ if (!disableConfigInjection) {
const appConfigs = await readConfigs({
config,
appDistDir,
@@ -131,7 +138,7 @@ export async function createRouter(
}),
);
- if (options.database) {
+ if (options.database && !disableStaticFallbackCache) {
const store = await StaticAssetsStore.create({
logger,
database: options.database,
diff --git a/plugins/app-node/.eslintrc.js b/plugins/app-node/.eslintrc.js
new file mode 100644
index 0000000000..e2a53a6ad2
--- /dev/null
+++ b/plugins/app-node/.eslintrc.js
@@ -0,0 +1 @@
+module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md
new file mode 100644
index 0000000000..1a2fd84689
--- /dev/null
+++ b/plugins/app-node/CHANGELOG.md
@@ -0,0 +1,23 @@
+# @backstage/plugin-app-node
+
+## 0.1.0
+
+### Minor Changes
+
+- 9fbe95ef6503: Added the `app` plugin node library, initially providing an extension point that can be used to configure a static fallback handler.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0
+
+## 0.1.0-next.0
+
+### Minor Changes
+
+- 9fbe95ef6503: Added the `app` plugin node library, initially providing an extension point that can be used to configure a static fallback handler.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
diff --git a/plugins/app-node/README.md b/plugins/app-node/README.md
new file mode 100644
index 0000000000..5c606540c0
--- /dev/null
+++ b/plugins/app-node/README.md
@@ -0,0 +1,5 @@
+# @backstage/plugin-app-node
+
+Welcome to the Node.js library package for the app plugin!
+
+_This plugin was created through the Backstage CLI_
diff --git a/plugins/app-node/api-report.md b/plugins/app-node/api-report.md
new file mode 100644
index 0000000000..504c9d48c1
--- /dev/null
+++ b/plugins/app-node/api-report.md
@@ -0,0 +1,16 @@
+## API Report File for "@backstage/plugin-app-node"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { ExtensionPoint } from '@backstage/backend-plugin-api';
+import { Handler } from 'express';
+
+// @public
+export interface StaticFallbackHandlerExtensionPoint {
+ setStaticFallbackHandler(handler: Handler): void;
+}
+
+// @public
+export const staticFallbackHandlerExtensionPoint: ExtensionPoint;
+```
diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json
new file mode 100644
index 0000000000..842086ecee
--- /dev/null
+++ b/plugins/app-node/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "@backstage/plugin-app-node",
+ "description": "Node.js library for the app plugin",
+ "version": "0.1.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.cjs.js",
+ "types": "dist/index.d.ts"
+ },
+ "backstage": {
+ "role": "node-library"
+ },
+ "scripts": {
+ "build": "backstage-cli package build",
+ "lint": "backstage-cli package lint",
+ "test": "backstage-cli package test",
+ "clean": "backstage-cli package clean",
+ "prepack": "backstage-cli package prepack",
+ "postpack": "backstage-cli package postpack"
+ },
+ "devDependencies": {
+ "@backstage/cli": "workspace:^"
+ },
+ "files": [
+ "dist"
+ ],
+ "dependencies": {
+ "@backstage/backend-plugin-api": "workspace:^",
+ "@types/express": "^4.17.6",
+ "express": "^4.17.1"
+ }
+}
diff --git a/plugins/app-node/src/extensions.ts b/plugins/app-node/src/extensions.ts
new file mode 100644
index 0000000000..6a75a5b791
--- /dev/null
+++ b/plugins/app-node/src/extensions.ts
@@ -0,0 +1,52 @@
+/*
+ * 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 { createExtensionPoint } from '@backstage/backend-plugin-api';
+import { Handler } from 'express';
+
+/**
+ * The interface for {@link staticFallbackHandlerExtensionPoint}.
+ *
+ * @public
+ */
+export interface StaticFallbackHandlerExtensionPoint {
+ /**
+ * Sets the static fallback handler. This can only be done once.
+ */
+ setStaticFallbackHandler(handler: Handler): void;
+}
+
+/**
+ * An extension point the exposes the ability to configure a static fallback handler for the app backend.
+ *
+ * The static fallback handler is a request handler to handle requests for static content that is not
+ * present in the app bundle.
+ *
+ * This can be used to avoid issues with clients on older deployment versions trying to access lazy
+ * loaded content that is no longer present. Typically the requests would fall back to a long-term
+ * object store where all recently deployed versions of the app are present.
+ *
+ * Another option is to provide a `database` that will take care of storing the static assets instead.
+ *
+ * If both `database` and `staticFallbackHandler` are provided, the `database` will attempt to serve
+ * static assets first, and if they are not found, the `staticFallbackHandler` will be called.
+ *
+ * @public
+ */
+export const staticFallbackHandlerExtensionPoint =
+ createExtensionPoint({
+ id: 'app.staticFallbackHandler',
+ });
diff --git a/plugins/app-node/src/index.ts b/plugins/app-node/src/index.ts
new file mode 100644
index 0000000000..c5189fa8b9
--- /dev/null
+++ b/plugins/app-node/src/index.ts
@@ -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.
+ */
+
+/**
+ * Node.js library for the app plugin.
+ *
+ * @packageDocumentation
+ */
+
+export {
+ staticFallbackHandlerExtensionPoint,
+ type StaticFallbackHandlerExtensionPoint,
+} from './extensions';
diff --git a/plugins/app-node/src/setupTests.ts b/plugins/app-node/src/setupTests.ts
new file mode 100644
index 0000000000..4b9026cde5
--- /dev/null
+++ b/plugins/app-node/src/setupTests.ts
@@ -0,0 +1,16 @@
+/*
+ * 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 {};
diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md
index 60555ef74f..5504f162e6 100644
--- a/plugins/auth-backend/CHANGELOG.md
+++ b/plugins/auth-backend/CHANGELOG.md
@@ -1,5 +1,45 @@
# @backstage/plugin-auth-backend
+## 0.18.6
+
+### Patch Changes
+
+- 16452cd007ae: Updated `frameHandler` to return `undefined` when using the redirect flow instead of returning `postMessageReponse` which was causing errors
+- 9dad4b0e61bd: Updated config schema to match what was being used in code
+- bb70a9c3886a: Add frontend visibility to provider objects in `auth` config.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/plugin-auth-node@0.2.17
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
+## 0.18.6-next.2
+
+### Patch Changes
+
+- 16452cd007ae: Updated `frameHandler` to return `undefined` when using the redirect flow instead of returning `postMessageReponse` which was causing errors
+- bb70a9c3886a: Add frontend visibility to provider objects in `auth` config.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-auth-node@0.2.17-next.2
+
+## 0.18.6-next.1
+
+### Patch Changes
+
+- 9dad4b0e61bd: Updated config schema to match what was being used in code
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-auth-node@0.2.17-next.1
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
## 0.18.6-next.0
### Patch Changes
diff --git a/plugins/auth-backend/config.d.ts b/plugins/auth-backend/config.d.ts
index c6579e08f8..4ebf7c9de1 100644
--- a/plugins/auth-backend/config.d.ts
+++ b/plugins/auth-backend/config.d.ts
@@ -62,6 +62,7 @@ export interface Config {
* @additionalProperties true
*/
providers?: {
+ /** @visibility frontend */
google?: {
[authEnv: string]: {
clientId: string;
@@ -72,6 +73,7 @@ export interface Config {
callbackUrl?: string;
};
};
+ /** @visibility frontend */
github?: {
[authEnv: string]: {
clientId: string;
@@ -83,6 +85,7 @@ export interface Config {
enterpriseInstanceUrl?: string;
};
};
+ /** @visibility frontend */
gitlab?: {
[authEnv: string]: {
clientId: string;
@@ -94,6 +97,7 @@ export interface Config {
callbackUrl?: string;
};
};
+ /** @visibility frontend */
saml?: {
entryPoint: string;
logoutUrl?: string;
@@ -117,6 +121,7 @@ export interface Config {
digestAlgorithm?: string;
acceptedClockSkewMs?: number;
};
+ /** @visibility frontend */
okta?: {
[authEnv: string]: {
clientId: string;
@@ -130,6 +135,7 @@ export interface Config {
callbackUrl?: string;
};
};
+ /** @visibility frontend */
oauth2?: {
[authEnv: string]: {
clientId: string;
@@ -143,6 +149,7 @@ export interface Config {
disableRefresh?: boolean;
};
};
+ /** @visibility frontend */
oidc?: {
[authEnv: string]: {
clientId: string;
@@ -152,10 +159,13 @@ export interface Config {
clientSecret: string;
callbackUrl?: string;
metadataUrl: string;
+ tokenEndpointAuthMethod?: string;
+ tokenSignedResponseAlg?: string;
scope?: string;
prompt?: string;
};
};
+ /** @visibility frontend */
auth0?: {
[authEnv: string]: {
clientId: string;
@@ -170,6 +180,7 @@ export interface Config {
connectionScope?: string;
};
};
+ /** @visibility frontend */
microsoft?: {
[authEnv: string]: {
clientId: string;
@@ -181,6 +192,7 @@ export interface Config {
callbackUrl?: string;
};
};
+ /** @visibility frontend */
onelogin?: {
[authEnv: string]: {
clientId: string;
@@ -192,10 +204,12 @@ export interface Config {
callbackUrl?: string;
};
};
+ /** @visibility frontend */
awsalb?: {
iss?: string;
region: string;
};
+ /** @visibility frontend */
cfaccess?: {
teamName: string;
};
diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json
index 4e7f80afaf..be2017e4db 100644
--- a/plugins/auth-backend/package.json
+++ b/plugins/auth-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-auth-backend",
"description": "A Backstage backend plugin that handles authentication",
- "version": "0.18.6-next.0",
+ "version": "0.18.6",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts
index a0c437815d..a61d51b27b 100644
--- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts
+++ b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts
@@ -125,7 +125,7 @@ describe('oauth helpers', () => {
postMessageResponse(mockResponse, appOrigin, data);
expect(responseBody.match(/.postMessage\(/g)).toHaveLength(2);
expect(
- responseBody.match(/.postMessage\([a-zA-z.()]*, \'\*\'\)/g),
+ responseBody.match(/.postMessage\([a-zA-Z.()]*, \'\*\'\)/g),
).toHaveLength(1);
const errData: WebMessageResponse = {
@@ -135,7 +135,7 @@ describe('oauth helpers', () => {
postMessageResponse(mockResponse, appOrigin, errData);
expect(responseBody.match(/.postMessage\(/g)).toHaveLength(2);
expect(
- responseBody.match(/.postMessage\([a-zA-z.()]*, \'\*\'\)/g),
+ responseBody.match(/.postMessage\([a-zA-Z.()]*, \'\*\'\)/g),
).toHaveLength(1);
});
diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts
index 5f51b2d301..fb0750f0cb 100644
--- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts
+++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts
@@ -187,6 +187,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
);
}
res.redirect(state.redirectUrl);
+ return undefined;
}
// post message back to popup if successful
return postMessageResponse(res, appOrigin, responseObj);
diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md
index cb802b9757..30c133338b 100644
--- a/plugins/auth-node/CHANGELOG.md
+++ b/plugins/auth-node/CHANGELOG.md
@@ -1,5 +1,32 @@
# @backstage/plugin-auth-node
+## 0.2.17
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+
+## 0.2.17-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.2
+
+## 0.2.17-next.1
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+
## 0.2.17-next.0
### Patch Changes
diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json
index 07b9adacb4..b5c402367b 100644
--- a/plugins/auth-node/package.json
+++ b/plugins/auth-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-node",
- "version": "0.2.17-next.0",
+ "version": "0.2.17",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -12,6 +12,12 @@
"backstage": {
"role": "node-library"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/auth-node"
+ },
"scripts": {
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md
index 8aa39659fa..fd9f614d75 100644
--- a/plugins/azure-devops-backend/CHANGELOG.md
+++ b/plugins/azure-devops-backend/CHANGELOG.md
@@ -1,5 +1,37 @@
# @backstage/plugin-azure-devops-backend
+## 0.3.27
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/config@1.0.8
+ - @backstage/plugin-azure-devops-common@0.3.0
+
+## 0.3.27-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-common@0.19.2-next.2
+
+## 0.3.27-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/config@1.0.8
+ - @backstage/plugin-azure-devops-common@0.3.0
+
## 0.3.27-next.0
### Patch Changes
diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json
index 0a34ab449b..6868de34fd 100644
--- a/plugins/azure-devops-backend/package.json
+++ b/plugins/azure-devops-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-azure-devops-backend",
- "version": "0.3.27-next.0",
+ "version": "0.3.27",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -12,6 +12,12 @@
"backstage": {
"role": "backend-plugin"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/azure-devops-backend"
+ },
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
diff --git a/plugins/azure-devops-backend/src/plugin.ts b/plugins/azure-devops-backend/src/plugin.ts
index 808bdfa248..ea0976a1f2 100644
--- a/plugins/azure-devops-backend/src/plugin.ts
+++ b/plugins/azure-devops-backend/src/plugin.ts
@@ -31,7 +31,7 @@ export const azureDevOpsPlugin = createBackendPlugin({
register(env) {
env.registerInit({
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
logger: coreServices.logger,
reader: coreServices.urlReader,
httpRouter: coreServices.httpRouter,
diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md
index 1b405ad870..abf3c3e146 100644
--- a/plugins/azure-devops/CHANGELOG.md
+++ b/plugins/azure-devops/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-azure-devops
+## 0.3.3
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.13.4
+ - @backstage/plugin-catalog-react@1.8.1
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/errors@1.2.1
+ - @backstage/theme@0.4.1
+ - @backstage/plugin-azure-devops-common@0.3.0
+
+## 0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.8.1-next.1
+
## 0.3.3-next.0
### Patch Changes
diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json
index daaedae825..96674d2cf2 100644
--- a/plugins/azure-devops/package.json
+++ b/plugins/azure-devops/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-azure-devops",
- "version": "0.3.3-next.0",
+ "version": "0.3.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/azure-sites-backend/CHANGELOG.md b/plugins/azure-sites-backend/CHANGELOG.md
index 6aa958a860..6e6a662283 100644
--- a/plugins/azure-sites-backend/CHANGELOG.md
+++ b/plugins/azure-sites-backend/CHANGELOG.md
@@ -1,5 +1,30 @@
# @backstage/plugin-azure-sites-backend
+## 0.1.10
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/config@1.0.8
+ - @backstage/plugin-azure-sites-common@0.1.0
+
+## 0.1.10-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.2
+
+## 0.1.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/config@1.0.8
+ - @backstage/plugin-azure-sites-common@0.1.0
+
## 0.1.10-next.0
### Patch Changes
diff --git a/plugins/azure-sites-backend/package.json b/plugins/azure-sites-backend/package.json
index 81d6c434a6..5b6f7ea83f 100644
--- a/plugins/azure-sites-backend/package.json
+++ b/plugins/azure-sites-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-azure-sites-backend",
- "version": "0.1.10-next.0",
+ "version": "0.1.10",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/azure-sites/CHANGELOG.md b/plugins/azure-sites/CHANGELOG.md
index 636c69c76a..6f043950c6 100644
--- a/plugins/azure-sites/CHANGELOG.md
+++ b/plugins/azure-sites/CHANGELOG.md
@@ -1,5 +1,24 @@
# @backstage/plugin-azure-sites
+## 0.1.10
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.13.4
+ - @backstage/plugin-catalog-react@1.8.1
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/theme@0.4.1
+ - @backstage/plugin-azure-sites-common@0.1.0
+
+## 0.1.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.8.1-next.1
+
## 0.1.10-next.0
### Patch Changes
diff --git a/plugins/azure-sites/package.json b/plugins/azure-sites/package.json
index 82b1bd1041..a4e50cccea 100644
--- a/plugins/azure-sites/package.json
+++ b/plugins/azure-sites/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-azure-sites",
- "version": "0.1.10-next.0",
+ "version": "0.1.10",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/badges-backend/CHANGELOG.md b/plugins/badges-backend/CHANGELOG.md
index 03ba2fa42b..2557b7469a 100644
--- a/plugins/badges-backend/CHANGELOG.md
+++ b/plugins/badges-backend/CHANGELOG.md
@@ -1,5 +1,42 @@
# @backstage/plugin-badges-backend
+## 0.2.3
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-auth-node@0.2.17
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+
+## 0.2.3-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-auth-node@0.2.17-next.2
+
+## 0.2.3-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-auth-node@0.2.17-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+
## 0.2.3-next.0
### Patch Changes
diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json
index 02f20826f2..f7acb9308f 100644
--- a/plugins/badges-backend/package.json
+++ b/plugins/badges-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-badges-backend",
"description": "A Backstage backend plugin that generates README badges for your entities",
- "version": "0.2.3-next.0",
+ "version": "0.2.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/badges-backend/src/plugin.ts b/plugins/badges-backend/src/plugin.ts
index ecc03f0c15..3e462e88e9 100644
--- a/plugins/badges-backend/src/plugin.ts
+++ b/plugins/badges-backend/src/plugin.ts
@@ -31,7 +31,7 @@ export const badgesPlugin = createBackendPlugin({
register(env) {
env.registerInit({
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
logger: coreServices.logger,
discovery: coreServices.discovery,
tokenManager: coreServices.tokenManager,
diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md
index 5d7527fba4..60e1ecc1be 100644
--- a/plugins/badges/CHANGELOG.md
+++ b/plugins/badges/CHANGELOG.md
@@ -1,5 +1,24 @@
# @backstage/plugin-badges
+## 0.2.45
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.13.4
+ - @backstage/plugin-catalog-react@1.8.1
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/errors@1.2.1
+ - @backstage/theme@0.4.1
+
+## 0.2.45-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.8.1-next.1
+
## 0.2.45-next.0
### Patch Changes
diff --git a/plugins/badges/package.json b/plugins/badges/package.json
index 79add2944c..9146f64de5 100644
--- a/plugins/badges/package.json
+++ b/plugins/badges/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-badges",
"description": "A Backstage plugin that generates README badges for your entities",
- "version": "0.2.45-next.0",
+ "version": "0.2.45",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/bazaar-backend/CHANGELOG.md b/plugins/bazaar-backend/CHANGELOG.md
index 15059fe6df..2cd3f586c2 100644
--- a/plugins/bazaar-backend/CHANGELOG.md
+++ b/plugins/bazaar-backend/CHANGELOG.md
@@ -1,5 +1,40 @@
# @backstage/plugin-bazaar-backend
+## 0.2.11
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-auth-node@0.2.17
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+
+## 0.2.11-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-auth-node@0.2.17-next.2
+
+## 0.2.11-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-auth-node@0.2.17-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+
## 0.2.11-next.0
### Patch Changes
diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json
index 6e960f384c..d97267faf8 100644
--- a/plugins/bazaar-backend/package.json
+++ b/plugins/bazaar-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-bazaar-backend",
- "version": "0.2.11-next.0",
+ "version": "0.2.11",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -13,6 +13,12 @@
"backstage": {
"role": "backend-plugin"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/bazaar-backend"
+ },
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build --experimental-type-build",
diff --git a/plugins/bazaar-backend/src/plugin.ts b/plugins/bazaar-backend/src/plugin.ts
index 62622717c8..1c06bda705 100644
--- a/plugins/bazaar-backend/src/plugin.ts
+++ b/plugins/bazaar-backend/src/plugin.ts
@@ -31,7 +31,7 @@ export const bazaarPlugin = createBackendPlugin({
register(env) {
env.registerInit({
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
database: coreServices.database,
identity: coreServices.identity,
logger: coreServices.logger,
diff --git a/plugins/bazaar/CHANGELOG.md b/plugins/bazaar/CHANGELOG.md
index 7168eaec27..b6bae0f5ed 100644
--- a/plugins/bazaar/CHANGELOG.md
+++ b/plugins/bazaar/CHANGELOG.md
@@ -1,5 +1,46 @@
# @backstage/plugin-bazaar
+## 0.2.12
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/cli@0.22.10
+ - @backstage/core-components@0.13.4
+ - @backstage/plugin-catalog@1.12.1
+ - @backstage/plugin-catalog-react@1.8.1
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/errors@1.2.1
+ - @backstage/theme@0.4.1
+
+## 0.2.12-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.8.1-next.1
+ - @backstage/plugin-catalog@1.12.1-next.2
+ - @backstage/cli@0.22.10-next.1
+
+## 0.2.12-next.1
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/cli@0.22.10-next.1
+ - @backstage/plugin-catalog@1.12.1-next.1
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/core-components@0.13.4-next.0
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/errors@1.2.1
+ - @backstage/theme@0.4.1
+ - @backstage/plugin-catalog-react@1.8.1-next.0
+
## 0.2.12-next.0
### Patch Changes
diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json
index 0536367512..34d7490554 100644
--- a/plugins/bazaar/package.json
+++ b/plugins/bazaar/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-bazaar",
- "version": "0.2.12-next.0",
+ "version": "0.2.12",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -12,6 +12,12 @@
"backstage": {
"role": "frontend-plugin"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/bazaar"
+ },
"scripts": {
"build": "backstage-cli package build",
"start": "backstage-cli package start",
diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md
index d438d843fd..f1a842f892 100644
--- a/plugins/bitbucket-cloud-common/CHANGELOG.md
+++ b/plugins/bitbucket-cloud-common/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-bitbucket-cloud-common
+## 0.2.9
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/integration@1.6.0
+
+## 0.2.9-next.0
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/integration@1.5.1
+
## 0.2.8
### Patch Changes
diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json
index ffbe670094..2bc9fc3297 100644
--- a/plugins/bitbucket-cloud-common/package.json
+++ b/plugins/bitbucket-cloud-common/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-bitbucket-cloud-common",
"description": "Common functionalities for bitbucket-cloud plugins",
- "version": "0.2.8",
+ "version": "0.2.9",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -14,6 +14,12 @@
"backstage": {
"role": "common-library"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/bitbucket-cloud-common"
+ },
"scripts": {
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md
index db09305c36..c44557a51c 100644
--- a/plugins/bitrise/CHANGELOG.md
+++ b/plugins/bitrise/CHANGELOG.md
@@ -1,5 +1,36 @@
# @backstage/plugin-bitrise
+## 0.1.48
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/core-components@0.13.4
+ - @backstage/plugin-catalog-react@1.8.1
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/theme@0.4.1
+
+## 0.1.48-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.8.1-next.1
+
+## 0.1.48-next.1
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/catalog-model@1.4.1
+ - @backstage/core-components@0.13.4-next.0
+ - @backstage/core-plugin-api@1.5.3
+ - @backstage/theme@0.4.1
+ - @backstage/plugin-catalog-react@1.8.1-next.0
+
## 0.1.48-next.0
### Patch Changes
diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json
index fe7f1f1591..6d63cb6384 100644
--- a/plugins/bitrise/package.json
+++ b/plugins/bitrise/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-bitrise",
"description": "A Backstage plugin that integrates towards Bitrise",
- "version": "0.1.48-next.0",
+ "version": "0.1.48",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -13,6 +13,12 @@
"backstage": {
"role": "frontend-plugin"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/bitrise"
+ },
"scripts": {
"build": "backstage-cli package build",
"start": "backstage-cli package start",
diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md
index 560e1cafb7..b2399d568f 100644
--- a/plugins/catalog-backend-module-aws/CHANGELOG.md
+++ b/plugins/catalog-backend-module-aws/CHANGELOG.md
@@ -1,5 +1,57 @@
# @backstage/plugin-catalog-backend-module-aws
+## 0.2.3
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- b4222908b0c3: Added option to configure AWS `accountId` in `AwsS3EntityProvider`
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/integration@1.6.0
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/integration-aws-node@0.1.5
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+ - @backstage/plugin-catalog-common@1.0.15
+ - @backstage/plugin-kubernetes-common@0.6.5
+
+## 0.2.3-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+
+## 0.2.3-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- b4222908b0c3: Added option to configure AWS `accountId` in `AwsS3EntityProvider`
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/integration@1.5.1
+ - @backstage/integration-aws-node@0.1.5
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+ - @backstage/plugin-catalog-common@1.0.15
+ - @backstage/plugin-kubernetes-common@0.6.5
+
## 0.2.3-next.0
### Patch Changes
diff --git a/plugins/catalog-backend-module-aws/config.d.ts b/plugins/catalog-backend-module-aws/config.d.ts
index b3610ccfc5..4c9c98512e 100644
--- a/plugins/catalog-backend-module-aws/config.d.ts
+++ b/plugins/catalog-backend-module-aws/config.d.ts
@@ -54,14 +54,19 @@ export interface Config {
* @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html
*/
region?: string;
+ /**
+ * (Optional) AWS Account id.
+ * If not set, main account is used.
+ * @see https://github.com/backstage/backstage/blob/master/packages/integration-aws-node/README.md
+ */
+ accountId?: string;
/**
* (Optional) TaskScheduleDefinition for the refresh.
*/
schedule?: TaskScheduleDefinitionConfig;
}
- | Record<
- string,
- {
+ | {
+ [name: string]: {
/**
* (Required) AWS S3 Bucket Name
*/
@@ -77,12 +82,18 @@ export interface Config {
* @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html
*/
region?: string;
+ /**
+ * (Optional) AWS Account id.
+ * If not set, main account is used.
+ * @see https://github.com/backstage/backstage/blob/master/packages/integration-aws-node/README.md
+ */
+ accountId?: string;
/**
* (Optional) TaskScheduleDefinition for the refresh.
*/
schedule?: TaskScheduleDefinitionConfig;
- }
- >;
+ };
+ };
};
};
}
diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json
index d27587a6b3..e593fcf90a 100644
--- a/plugins/catalog-backend-module-aws/package.json
+++ b/plugins/catalog-backend-module-aws/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-aws",
"description": "A Backstage catalog backend module that helps integrate towards AWS",
- "version": "0.2.3-next.0",
+ "version": "0.2.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.test.ts b/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.test.ts
index 1475da9e79..c9fea8aa28 100644
--- a/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.test.ts
+++ b/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.test.ts
@@ -15,13 +15,15 @@
*/
import { getVoidLogger } from '@backstage/backend-common';
-import { coreServices } from '@backstage/backend-plugin-api';
+import {
+ coreServices,
+ createServiceFactory,
+} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
-import { startTestBackend } from '@backstage/backend-test-utils';
-import { ConfigReader } from '@backstage/config';
+import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { Duration } from 'luxon';
import { catalogModuleAwsS3EntityProvider } from './catalogModuleAwsS3EntityProvider';
@@ -45,7 +47,7 @@ describe('catalogModuleAwsS3EntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
- const config = new ConfigReader({
+ const config = {
catalog: {
providers: {
awsS3: {
@@ -57,16 +59,24 @@ describe('catalogModuleAwsS3EntityProvider', () => {
},
},
},
- });
+ };
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
- services: [
- [coreServices.config, config],
- [coreServices.logger, getVoidLogger()],
- [coreServices.scheduler, scheduler],
+ features: [
+ catalogModuleAwsS3EntityProvider(),
+ mockServices.rootConfig.factory({ data: config }),
+ createServiceFactory(() => ({
+ service: coreServices.logger,
+ deps: {},
+ factory: getVoidLogger,
+ }))(),
+ createServiceFactory(() => ({
+ service: coreServices.scheduler,
+ deps: {},
+ factory: () => scheduler,
+ }))(),
],
- features: [catalogModuleAwsS3EntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
diff --git a/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.ts b/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.ts
index 452284e526..521d61d3d8 100644
--- a/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.ts
+++ b/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.ts
@@ -33,7 +33,7 @@ export const catalogModuleAwsS3EntityProvider = createBackendModule({
register(env) {
env.registerInit({
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
catalog: catalogProcessingExtensionPoint,
logger: coreServices.logger,
scheduler: coreServices.scheduler,
diff --git a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts
index 4a87a42e75..4df92b58c2 100644
--- a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts
+++ b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts
@@ -146,20 +146,22 @@ export class AwsS3EntityProvider implements EntityProvider {
/** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */
async connect(connection: EntityProviderConnection): Promise {
this.connection = connection;
- const credProvider =
- await this.awsCredentialsManager.getCredentialProvider();
+ const { accountId, region, bucketName } = this.config;
+ const credProvider = await this.awsCredentialsManager.getCredentialProvider(
+ accountId ? { accountId } : undefined,
+ );
this.s3 = new S3({
apiVersion: '2006-03-01',
credentialDefaultProvider: () => credProvider.sdkCredentialProvider,
endpoint: this.integration.config.endpoint,
- region: this.config.region,
+ region,
forcePathStyle: this.integration.config.s3ForcePathStyle,
});
// https://github.com/aws/aws-sdk-js-v3/issues/4122#issuecomment-1298968804
const endpoint = await getEndpointFromInstructions(
{
- Bucket: this.config.bucketName,
+ Bucket: bucketName,
},
ListObjectsV2Command,
this.s3.config as unknown as Record,
diff --git a/plugins/catalog-backend-module-aws/src/providers/config.ts b/plugins/catalog-backend-module-aws/src/providers/config.ts
index 5ef2729bf0..1ab52f9fd4 100644
--- a/plugins/catalog-backend-module-aws/src/providers/config.ts
+++ b/plugins/catalog-backend-module-aws/src/providers/config.ts
@@ -46,6 +46,7 @@ function readAwsS3Config(id: string, config: Config): AwsS3Config {
const bucketName = config.getString('bucketName');
const region = config.getOptionalString('region');
const prefix = config.getOptionalString('prefix');
+ const accountId = config.getOptionalString('accountId');
const schedule = config.has('schedule')
? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule'))
@@ -57,5 +58,6 @@ function readAwsS3Config(id: string, config: Config): AwsS3Config {
region,
prefix,
schedule,
+ accountId,
};
}
diff --git a/plugins/catalog-backend-module-aws/src/providers/types.ts b/plugins/catalog-backend-module-aws/src/providers/types.ts
index 204fa154c6..8e12b5f096 100644
--- a/plugins/catalog-backend-module-aws/src/providers/types.ts
+++ b/plugins/catalog-backend-module-aws/src/providers/types.ts
@@ -22,4 +22,5 @@ export type AwsS3Config = {
prefix?: string;
region?: string;
schedule?: TaskScheduleDefinition;
+ accountId?: string;
};
diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md
index 9d60b4a5f2..73b5489600 100644
--- a/plugins/catalog-backend-module-azure/CHANGELOG.md
+++ b/plugins/catalog-backend-module-azure/CHANGELOG.md
@@ -1,5 +1,51 @@
# @backstage/plugin-catalog-backend-module-azure
+## 0.1.19
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/integration@1.6.0
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+ - @backstage/plugin-catalog-common@1.0.15
+
+## 0.1.19-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+
+## 0.1.19-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/integration@1.5.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+ - @backstage/plugin-catalog-common@1.0.15
+
## 0.1.19-next.0
### Patch Changes
diff --git a/plugins/catalog-backend-module-azure/config.d.ts b/plugins/catalog-backend-module-azure/config.d.ts
index 940695c8ea..757ed2bfd3 100644
--- a/plugins/catalog-backend-module-azure/config.d.ts
+++ b/plugins/catalog-backend-module-azure/config.d.ts
@@ -16,35 +16,6 @@
import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks';
-interface AzureDevOpsConfig {
- /**
- * (Optional) The DevOps host; leave empty for `dev.azure.com`, otherwise set to your self-hosted instance host.
- */
- host: string;
- /**
- * (Required) Your organization slug.
- */
- organization: string;
- /**
- * (Required) Your project slug.
- */
- project: string;
- /**
- * (Optional) The repository name. Wildcards are supported as show on the examples above.
- * If not set, all repositories will be searched.
- */
- repository?: string;
- /**
- * (Optional) Where to find catalog-info.yaml files. Wildcards are supported.
- * If not set, defaults to /catalog-info.yaml.
- */
- path?: string;
- /**
- * (Optional) TaskScheduleDefinition for the refresh.
- */
- schedule?: TaskScheduleDefinitionConfig;
-}
-
export interface Config {
catalog?: {
/**
@@ -54,7 +25,36 @@ export interface Config {
/**
* AzureDevopsEntityProvider configuration
*/
- azureDevOps?: Record;
+ azureDevOps?: {
+ [name: string]: {
+ /**
+ * (Optional) The DevOps host; leave empty for `dev.azure.com`, otherwise set to your self-hosted instance host.
+ */
+ host: string;
+ /**
+ * (Required) Your organization slug.
+ */
+ organization: string;
+ /**
+ * (Required) Your project slug.
+ */
+ project: string;
+ /**
+ * (Optional) The repository name. Wildcards are supported as show on the examples above.
+ * If not set, all repositories will be searched.
+ */
+ repository?: string;
+ /**
+ * (Optional) Where to find catalog-info.yaml files. Wildcards are supported.
+ * If not set, defaults to /catalog-info.yaml.
+ */
+ path?: string;
+ /**
+ * (Optional) TaskScheduleDefinition for the refresh.
+ */
+ schedule?: TaskScheduleDefinitionConfig;
+ };
+ };
};
};
}
diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json
index ac3cdd2b05..eb6a3eadf5 100644
--- a/plugins/catalog-backend-module-azure/package.json
+++ b/plugins/catalog-backend-module-azure/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-azure",
"description": "A Backstage catalog backend module that helps integrate towards Azure",
- "version": "0.1.19-next.0",
+ "version": "0.1.19",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.test.ts b/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.test.ts
index fa3b3d865e..3147a0871b 100644
--- a/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.test.ts
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-import { getVoidLogger } from '@backstage/backend-common';
-import { coreServices } from '@backstage/backend-plugin-api';
+import {
+ coreServices,
+ createServiceFactory,
+} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
-import { startTestBackend } from '@backstage/backend-test-utils';
-import { ConfigReader } from '@backstage/config';
+import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { Duration } from 'luxon';
import { catalogModuleAzureDevOpsEntityProvider } from './catalogModuleAzureDevOpsEntityProvider';
@@ -45,7 +46,7 @@ describe('catalogModuleAzureDevOpsEntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
- const config = new ConfigReader({
+ const config = {
catalog: {
providers: {
azureDevOps: {
@@ -60,16 +61,20 @@ describe('catalogModuleAzureDevOpsEntityProvider', () => {
},
},
},
- });
+ };
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
- services: [
- [coreServices.config, config],
- [coreServices.logger, getVoidLogger()],
- [coreServices.scheduler, scheduler],
+ features: [
+ catalogModuleAzureDevOpsEntityProvider(),
+ mockServices.rootConfig.factory({ data: config }),
+ mockServices.logger.factory(),
+ createServiceFactory(() => ({
+ deps: {},
+ service: coreServices.scheduler,
+ factory: async () => scheduler,
+ })),
],
- features: [catalogModuleAzureDevOpsEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
diff --git a/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts b/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts
index ec8b6441ba..804c0d4712 100644
--- a/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts
+++ b/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts
@@ -33,7 +33,7 @@ export const catalogModuleAzureDevOpsEntityProvider = createBackendModule({
register(env) {
env.registerInit({
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
catalog: catalogProcessingExtensionPoint,
logger: coreServices.logger,
scheduler: coreServices.scheduler,
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md
index 4aeca8609e..7503ee898e 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md
+++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md
@@ -1,5 +1,54 @@
# @backstage/plugin-catalog-backend-module-bitbucket-cloud
+## 0.1.15
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-bitbucket-cloud-common@0.2.9
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/plugin-events-node@0.2.9
+ - @backstage/integration@1.6.0
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/plugin-catalog-common@1.0.15
+
+## 0.1.15-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+ - @backstage/plugin-events-node@0.2.9-next.2
+
+## 0.1.15-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-bitbucket-cloud-common@0.2.9-next.0
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/plugin-events-node@0.2.9-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/integration@1.5.1
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/plugin-catalog-common@1.0.15
+
## 0.1.15-next.0
### Patch Changes
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts
index 8fa58413c0..fa7c070042 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts
+++ b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts
@@ -45,24 +45,23 @@ export interface Config {
*/
filters?: {
/**
- * (Optional) Filter for the repository slug.
+ * (Optional) Regular expression filter for the repository slug.
* @visibility frontend
*/
- repoSlug?: RegExp;
+ repoSlug?: string;
/**
- * (Optional) Filter for the project key.
+ * (Optional) Regular expression filter for the project key.
* @visibility frontend
*/
- projectKey?: RegExp;
+ projectKey?: string;
};
/**
* (Optional) TaskScheduleDefinition for the discovery.
*/
schedule?: TaskScheduleDefinitionConfig;
}
- | Record<
- string,
- {
+ | {
+ [name: string]: {
/**
* (Optional) Path to the catalog file. Default to "/catalog-info.yaml".
* @visibility frontend
@@ -79,22 +78,22 @@ export interface Config {
*/
filters?: {
/**
- * (Optional) Filter for the repository slug.
+ * (Optional) Regular expression filter for the repository slug.
* @visibility frontend
*/
- repoSlug?: RegExp;
+ repoSlug?: string;
/**
- * (Optional) Filter for the project key.
+ * (Optional) Regular expression filter for the project key.
* @visibility frontend
*/
- projectKey?: RegExp;
+ projectKey?: string;
};
/**
* (Optional) TaskScheduleDefinition for the discovery.
*/
schedule?: TaskScheduleDefinitionConfig;
- }
- >;
+ };
+ };
};
};
}
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json
index 1baa14f00f..42df54688b 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/package.json
+++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud",
"description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud",
- "version": "0.1.15-next.0",
+ "version": "0.1.15",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts
index a34eae3750..83769fc3f5 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-import { coreServices } from '@backstage/backend-plugin-api';
+import {
+ coreServices,
+ createServiceFactory,
+} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
@@ -55,8 +58,9 @@ describe('catalogModuleBitbucketCloudEntityProvider', () => {
[catalogProcessingExtensionPoint, catalogExtensionPointImpl],
[eventsExtensionPoint, eventsExtensionPointImpl],
],
- services: [
- mockServices.config.factory({
+ features: [
+ catalogModuleBitbucketCloudEntityProvider(),
+ mockServices.rootConfig.factory({
data: {
catalog: {
providers: {
@@ -71,9 +75,12 @@ describe('catalogModuleBitbucketCloudEntityProvider', () => {
},
},
}),
- [coreServices.scheduler, scheduler],
+ createServiceFactory({
+ service: coreServices.scheduler,
+ deps: {},
+ factory: async () => scheduler,
+ }),
],
- features: [catalogModuleBitbucketCloudEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts
index 7c4f4a30b2..e5e1ad1609 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts
+++ b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts
@@ -37,7 +37,7 @@ export const catalogModuleBitbucketCloudEntityProvider = createBackendModule({
deps: {
catalog: catalogProcessingExtensionPoint,
catalogApi: catalogServiceRef,
- config: coreServices.config,
+ config: coreServices.rootConfig,
// TODO(pjungermann): How to make this optional for those which only want the provider without event support?
// Do we even want to support this?
events: eventsExtensionPoint,
diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md
index fa0c443e3a..1b5a65f254 100644
--- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md
+++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md
@@ -1,5 +1,47 @@
# @backstage/plugin-catalog-backend-module-bitbucket-server
+## 0.1.13
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/integration@1.6.0
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+
+## 0.1.13-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+
+## 0.1.13-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/integration@1.5.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+
## 0.1.13-next.0
### Patch Changes
diff --git a/plugins/catalog-backend-module-bitbucket-server/config.d.ts b/plugins/catalog-backend-module-bitbucket-server/config.d.ts
index 67f694d419..57a94550e9 100644
--- a/plugins/catalog-backend-module-bitbucket-server/config.d.ts
+++ b/plugins/catalog-backend-module-bitbucket-server/config.d.ts
@@ -37,24 +37,23 @@ export interface Config {
*/
filters?: {
/**
- * (Optional) Filter for the repository slug.
+ * (Optional) Regular expression filter for the repository slug.
* @visibility frontend
*/
- repoSlug?: RegExp;
+ repoSlug?: string;
/**
- * (Optional) Filter for the project key.
+ * (Optional) Regular expression filter for the project key.
* @visibility frontend
*/
- projectKey?: RegExp;
+ projectKey?: string;
};
/**
* (Optional) TaskScheduleDefinition for the refresh.
*/
schedule?: TaskScheduleDefinitionConfig;
}
- | Record<
- string,
- {
+ | {
+ [name: string]: {
/**
* (Optional) Path to the catalog file. Default to "/catalog-info.yaml".
* @visibility frontend
@@ -66,22 +65,22 @@ export interface Config {
*/
filters?: {
/**
- * (Optional) Filter for the repository slug.
+ * (Optional) Regular expression filter for the repository slug.
* @visibility frontend
*/
- repoSlug?: RegExp;
+ repoSlug?: string;
/**
- * (Optional) Filter for the project key.
+ * (Optional) Regular expression filter for the project key.
* @visibility frontend
*/
- projectKey?: RegExp;
+ projectKey?: string;
};
/**
* (Optional) TaskScheduleDefinition for the refresh.
*/
schedule?: TaskScheduleDefinitionConfig;
- }
- >;
+ };
+ };
};
};
}
diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json
index 4b35c8f7d7..7e488e31ff 100644
--- a/plugins/catalog-backend-module-bitbucket-server/package.json
+++ b/plugins/catalog-backend-module-bitbucket-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-bitbucket-server",
- "version": "0.1.13-next.0",
+ "version": "0.1.13",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.test.ts
index d17b4a4c96..ae4a636008 100644
--- a/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.test.ts
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-import { ConfigReader } from '@backstage/config';
-import { getVoidLogger } from '@backstage/backend-common';
-import { coreServices } from '@backstage/backend-plugin-api';
+import {
+ coreServices,
+ createServiceFactory,
+} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
-import { startTestBackend } from '@backstage/backend-test-utils';
+import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogModuleBitbucketServerEntityProvider } from './catalogModuleBitbucketServerEntityProvider';
import { Duration } from 'luxon';
@@ -45,7 +46,7 @@ describe('catalogModuleBitbucketServerEntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
- const config = new ConfigReader({
+ const config = {
catalog: {
providers: {
bitbucketServer: {
@@ -64,16 +65,20 @@ describe('catalogModuleBitbucketServerEntityProvider', () => {
},
],
},
- });
+ };
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
- services: [
- [coreServices.config, config],
- [coreServices.logger, getVoidLogger()],
- [coreServices.scheduler, scheduler],
+ features: [
+ catalogModuleBitbucketServerEntityProvider(),
+ mockServices.rootConfig.factory({ data: config }),
+ mockServices.logger.factory(),
+ createServiceFactory({
+ service: coreServices.scheduler,
+ deps: {},
+ factory: async () => scheduler,
+ }),
],
- features: [catalogModuleBitbucketServerEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
diff --git a/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.ts
index 354bce7d6d..ef71df9b06 100644
--- a/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.ts
+++ b/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.ts
@@ -32,7 +32,7 @@ export const catalogModuleBitbucketServerEntityProvider = createBackendModule({
env.registerInit({
deps: {
catalog: catalogProcessingExtensionPoint,
- config: coreServices.config,
+ config: coreServices.rootConfig,
logger: coreServices.logger,
scheduler: coreServices.scheduler,
},
diff --git a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md
index 9c1da14fba..ef448f6b20 100644
--- a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md
+++ b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md
@@ -1,5 +1,41 @@
# @backstage/plugin-catalog-backend-module-bitbucket
+## 0.2.15
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/plugin-bitbucket-cloud-common@0.2.9
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/integration@1.6.0
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
+## 0.2.15-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+
+## 0.2.15-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-bitbucket-cloud-common@0.2.9-next.0
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/integration@1.5.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
## 0.2.15-next.0
### Patch Changes
diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json
index e968aad343..45ca64dc71 100644
--- a/plugins/catalog-backend-module-bitbucket/package.json
+++ b/plugins/catalog-backend-module-bitbucket/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-bitbucket",
"description": "A Backstage catalog backend module that helps integrate towards Bitbucket",
- "version": "0.2.15-next.0",
+ "version": "0.2.15",
"deprecated": true,
"main": "src/index.ts",
"types": "src/index.ts",
diff --git a/plugins/catalog-backend-module-gcp/.eslintrc.js b/plugins/catalog-backend-module-gcp/.eslintrc.js
new file mode 100644
index 0000000000..e2a53a6ad2
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/.eslintrc.js
@@ -0,0 +1 @@
+module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md
new file mode 100644
index 0000000000..c30f91fa61
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md
@@ -0,0 +1,45 @@
+# @backstage/plugin-catalog-backend-module-gcp
+
+## 0.1.0
+
+### Minor Changes
+
+- 290eff6692aa: Added GCP catalog plugin with GKE provider
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/config@1.0.8
+ - @backstage/plugin-kubernetes-common@0.6.5
+
+## 0.1.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+
+## 0.1.0-next.0
+
+### Minor Changes
+
+- 290eff6692aa: Added GCP catalog plugin with GKE provider
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/config@1.0.8
+ - @backstage/plugin-kubernetes-common@0.6.5
diff --git a/plugins/catalog-backend-module-gcp/README.md b/plugins/catalog-backend-module-gcp/README.md
new file mode 100644
index 0000000000..d1ef926310
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/README.md
@@ -0,0 +1,41 @@
+# Catalog Backend Module for GCP
+
+This is an extension module to the plugin-catalog-backend plugin, containing catalog processors and providers to ingest GCP resources as `Resource` kind entities.
+
+## installation
+
+Register the plugin in `catalog.ts``
+
+```typescript
+import { GkeEntityProvider } from '@backstage/plugin-catalog-backend-module-gcp';
+
+...
+
+builder.addEntityProvider(
+ GkeEntityProvider.fromConfig({
+ logger: env.logger,
+ scheduler: env.scheduler,
+ config: env.config
+ })
+);
+```
+
+Update `app-config.yaml` as follows:
+
+```yaml
+catalog:
+ providers:
+ gcp:
+ gke:
+ parents:
+ # consult https://cloud.google.com/kubernetes-engine/docs/ for valid values
+ # list all clusters in the project
+ - 'projects/some-project/locations/-'
+ # list all clusters in the region, in the project
+ - 'projects/some-other-project/locations/some-region'
+ schedule: # optional; same options as in TaskScheduleDefinition
+ # supports cron, ISO duration, "human duration" as used in code
+ frequency: { minutes: 30 }
+ # supports ISO duration, "human duration" as used in code
+ timeout: { minutes: 3 }
+```
diff --git a/plugins/catalog-backend-module-gcp/alpha-api-report.md b/plugins/catalog-backend-module-gcp/alpha-api-report.md
new file mode 100644
index 0000000000..6e6f0427e9
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/alpha-api-report.md
@@ -0,0 +1,7 @@
+## API Report File for "@backstage/plugin-catalog-backend-module-gcp"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+
+```
diff --git a/plugins/catalog-backend-module-gcp/api-report.md b/plugins/catalog-backend-module-gcp/api-report.md
new file mode 100644
index 0000000000..6f963ed51d
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/api-report.md
@@ -0,0 +1,48 @@
+## API Report File for "@backstage/plugin-catalog-backend-module-gcp"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { BackendFeature } from '@backstage/backend-plugin-api';
+import { Config } from '@backstage/config';
+import * as container from '@google-cloud/container';
+import { EntityProvider } from '@backstage/plugin-catalog-node';
+import { EntityProviderConnection } from '@backstage/plugin-catalog-node';
+import { Logger } from 'winston';
+import { SchedulerService } from '@backstage/backend-plugin-api';
+
+// @public
+export const catalogModuleGcpGkeEntityProvider: () => BackendFeature;
+
+// @public
+export class GkeEntityProvider implements EntityProvider {
+ // (undocumented)
+ connect(connection: EntityProviderConnection): Promise;
+ // (undocumented)
+ static fromConfig({
+ logger,
+ scheduler,
+ config,
+ }: {
+ logger: Logger;
+ scheduler: SchedulerService;
+ config: Config;
+ }): GkeEntityProvider;
+ // (undocumented)
+ static fromConfigWithClient({
+ logger,
+ scheduler,
+ config,
+ clusterManagerClient,
+ }: {
+ logger: Logger;
+ scheduler: SchedulerService;
+ config: Config;
+ clusterManagerClient: container.v1.ClusterManagerClient;
+ }): GkeEntityProvider;
+ // (undocumented)
+ getProviderName(): string;
+ // (undocumented)
+ refresh(): Promise;
+}
+```
diff --git a/plugins/catalog-backend-module-gcp/config.d.ts b/plugins/catalog-backend-module-gcp/config.d.ts
new file mode 100644
index 0000000000..d6884d32ce
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/config.d.ts
@@ -0,0 +1,45 @@
+/*
+ * 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 { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks';
+
+export interface Config {
+ catalog?: {
+ /**
+ * List of provider-specific options and attributes
+ */
+ providers?: {
+ /**
+ * GCPCatalogModuleConfig configuration
+ */
+ gcp?: {
+ /**
+ * Config for GKE clusters
+ */
+ gke?: {
+ /**
+ * Locations to list clusters from
+ */
+ parents: string[];
+ /**
+ * (Optional) TaskScheduleDefinition for the refresh.
+ */
+ schedule: TaskScheduleDefinitionConfig;
+ };
+ };
+ };
+ };
+}
diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json
new file mode 100644
index 0000000000..dcff6662dd
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/package.json
@@ -0,0 +1,66 @@
+{
+ "name": "@backstage/plugin-catalog-backend-module-gcp",
+ "description": "A Backstage catalog backend module that helps integrate towards GCP",
+ "version": "0.1.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public"
+ },
+ "exports": {
+ ".": "./src/index.ts",
+ "./alpha": "./src/alpha.ts",
+ "./package.json": "./package.json"
+ },
+ "typesVersions": {
+ "*": {
+ "alpha": [
+ "src/alpha.ts"
+ ],
+ "package.json": [
+ "package.json"
+ ]
+ }
+ },
+ "backstage": {
+ "role": "backend-plugin-module"
+ },
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/catalog-backend-module-gcp"
+ },
+ "keywords": [
+ "backstage"
+ ],
+ "scripts": {
+ "start": "backstage-cli package start",
+ "build": "backstage-cli package build",
+ "lint": "backstage-cli package lint",
+ "test": "backstage-cli package test",
+ "prepack": "backstage-cli package prepack",
+ "postpack": "backstage-cli package postpack",
+ "clean": "backstage-cli package clean"
+ },
+ "dependencies": {
+ "@backstage/backend-common": "workspace:^",
+ "@backstage/backend-plugin-api": "workspace:^",
+ "@backstage/backend-tasks": "workspace:^",
+ "@backstage/config": "workspace:^",
+ "@backstage/plugin-catalog-node": "workspace:^",
+ "@backstage/plugin-kubernetes-common": "workspace:^",
+ "@google-cloud/container": "^4.15.0",
+ "winston": "^3.2.1"
+ },
+ "devDependencies": {
+ "@backstage/backend-test-utils": "workspace:^",
+ "@backstage/cli": "workspace:^"
+ },
+ "files": [
+ "config.d.ts",
+ "dist"
+ ],
+ "configSchema": "config.d.ts"
+}
diff --git a/plugins/catalog-backend-module-gcp/src/alpha.ts b/plugins/catalog-backend-module-gcp/src/alpha.ts
new file mode 100644
index 0000000000..9eec69e76f
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/src/alpha.ts
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * A Backstage catalog backend module that helps integrate towards GCP
+ *
+ * @packageDocumentation
+ */
+
+export {};
diff --git a/plugins/catalog-backend-module-gcp/src/index.ts b/plugins/catalog-backend-module-gcp/src/index.ts
new file mode 100644
index 0000000000..fb3984a04d
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/src/index.ts
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+/**
+ * A Backstage catalog backend module that helps integrate towards GCP
+ *
+ * @packageDocumentation
+ */
+
+export * from './providers';
+export * from './module';
diff --git a/plugins/catalog-backend-module-gcp/src/module/catalogModuleGcpGkeEntityProvider.ts b/plugins/catalog-backend-module-gcp/src/module/catalogModuleGcpGkeEntityProvider.ts
new file mode 100644
index 0000000000..7f93bddbfe
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/src/module/catalogModuleGcpGkeEntityProvider.ts
@@ -0,0 +1,52 @@
+/*
+ * 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 { loggerToWinstonLogger } from '@backstage/backend-common';
+import {
+ coreServices,
+ createBackendModule,
+} from '@backstage/backend-plugin-api';
+import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
+import { GkeEntityProvider } from '../providers/GkeEntityProvider';
+
+/**
+ * Registers the GcpGkeEntityProvider with the catalog processing extension point.
+ *
+ * @public
+ */
+export const catalogModuleGcpGkeEntityProvider = createBackendModule({
+ pluginId: 'catalog',
+ moduleId: 'gcpGkeEntityProvider',
+ register(env) {
+ env.registerInit({
+ deps: {
+ config: coreServices.rootConfig,
+ catalog: catalogProcessingExtensionPoint,
+ logger: coreServices.logger,
+ scheduler: coreServices.scheduler,
+ },
+ async init({ config, catalog, logger, scheduler }) {
+ catalog.addEntityProvider(
+ GkeEntityProvider.fromConfig({
+ logger: loggerToWinstonLogger(logger),
+ scheduler,
+ config,
+ }),
+ );
+ },
+ });
+ },
+});
diff --git a/plugins/catalog-backend-module-gcp/src/module/index.ts b/plugins/catalog-backend-module-gcp/src/module/index.ts
new file mode 100644
index 0000000000..c7cd310c97
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/src/module/index.ts
@@ -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 { catalogModuleGcpGkeEntityProvider } from './catalogModuleGcpGkeEntityProvider';
diff --git a/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts
new file mode 100644
index 0000000000..20e91a85bf
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts
@@ -0,0 +1,251 @@
+/*
+ * 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 { GkeEntityProvider } from './GkeEntityProvider';
+import { TaskRunner } from '@backstage/backend-tasks';
+import {
+ ANNOTATION_KUBERNETES_API_SERVER,
+ ANNOTATION_KUBERNETES_API_SERVER_CA,
+ ANNOTATION_KUBERNETES_AUTH_PROVIDER,
+} from '@backstage/plugin-kubernetes-common';
+import * as container from '@google-cloud/container';
+import { ConfigReader } from '@backstage/config';
+
+describe('GkeEntityProvider', () => {
+ const clusterManagerClientMock = {
+ listClusters: jest.fn(),
+ };
+ const connectionMock = {
+ applyMutation: jest.fn(),
+ refresh: jest.fn(),
+ };
+ const taskRunner = {
+ createScheduleFn: jest.fn(),
+ run: jest.fn(),
+ } as TaskRunner;
+ const schedulerMock = {
+ createScheduledTaskRunner: jest.fn(),
+ } as any;
+ const logger = {
+ info: jest.fn(),
+ error: jest.fn(),
+ };
+ let gkeEntityProvider: GkeEntityProvider;
+
+ beforeEach(async () => {
+ jest.resetAllMocks();
+ schedulerMock.createScheduledTaskRunner.mockReturnValue(taskRunner);
+ gkeEntityProvider = GkeEntityProvider.fromConfigWithClient({
+ logger: logger as any,
+ config: new ConfigReader({
+ catalog: {
+ providers: {
+ gcp: {
+ gke: {
+ parents: ['parent1', 'parent2'],
+ schedule: {
+ frequency: {
+ minutes: 3,
+ },
+ timeout: {
+ minutes: 3,
+ },
+ },
+ },
+ },
+ },
+ },
+ }),
+ scheduler: schedulerMock,
+ clusterManagerClient: clusterManagerClientMock as any,
+ });
+ await gkeEntityProvider.connect(connectionMock);
+ });
+
+ it('should return clusters as Resources', async () => {
+ clusterManagerClientMock.listClusters.mockImplementation(req => {
+ if (req.parent === 'parent1') {
+ return [
+ {
+ clusters: [
+ {
+ name: 'some-cluster',
+ endpoint: 'http://127.0.0.1:1234',
+ location: 'some-location',
+ selfLink: 'http://127.0.0.1/some-link',
+ masterAuth: {
+ clusterCaCertificate: 'abcdefg',
+ },
+ },
+ ],
+ },
+ ];
+ } else if (req.parent === 'parent2') {
+ return [
+ {
+ clusters: [
+ {
+ name: 'some-other-cluster',
+ endpoint: 'http://127.0.0.1:5678',
+ location: 'some-other-location',
+ selfLink: 'http://127.0.0.1/some-other-link',
+ masterAuth: {
+ // no CA cert is ok
+ },
+ },
+ ],
+ },
+ ];
+ }
+
+ throw new Error(`unexpected parent ${req.parent}`);
+ });
+ await gkeEntityProvider.refresh();
+ expect(connectionMock.applyMutation).toHaveBeenCalledWith({
+ type: 'full',
+ entities: [
+ {
+ locationKey: 'gcp-gke:some-location',
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Resource',
+ metadata: {
+ annotations: {
+ [ANNOTATION_KUBERNETES_API_SERVER]: 'http://127.0.0.1:1234',
+ [ANNOTATION_KUBERNETES_API_SERVER_CA]: 'abcdefg',
+ [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google',
+ 'backstage.io/managed-by-location': 'gcp-gke:some-location',
+ 'backstage.io/managed-by-origin-location':
+ 'gcp-gke:some-location',
+ },
+ name: 'some-cluster',
+ namespace: 'default',
+ },
+ spec: {
+ type: 'kubernetes-cluster',
+ owner: 'unknown',
+ },
+ },
+ },
+ {
+ locationKey: 'gcp-gke:some-other-location',
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Resource',
+ metadata: {
+ annotations: {
+ [ANNOTATION_KUBERNETES_API_SERVER]: 'http://127.0.0.1:5678',
+ [ANNOTATION_KUBERNETES_API_SERVER_CA]: '',
+ [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google',
+ 'backstage.io/managed-by-location':
+ 'gcp-gke:some-other-location',
+ 'backstage.io/managed-by-origin-location':
+ 'gcp-gke:some-other-location',
+ },
+ name: 'some-other-cluster',
+ namespace: 'default',
+ },
+ spec: {
+ type: 'kubernetes-cluster',
+ owner: 'unknown',
+ },
+ },
+ },
+ ],
+ });
+ });
+
+ const ignoredPartialClustersTests: [
+ string,
+ container.protos.google.container.v1.ICluster,
+ ][] = [
+ [
+ 'no-cluster-name',
+ {
+ endpoint: 'http://127.0.0.1:1234',
+ location: 'some-location',
+ selfLink: 'http://127.0.0.1/some-link',
+ masterAuth: {
+ clusterCaCertificate: 'abcdefg',
+ },
+ },
+ ],
+ [
+ 'no-self-link',
+ {
+ // no selfLink
+ name: 'some-name',
+ endpoint: 'http://127.0.0.1:1234',
+ location: 'some-location',
+ masterAuth: {
+ clusterCaCertificate: 'abcdefg',
+ },
+ },
+ ],
+ [
+ 'no-endpoint',
+ {
+ name: 'some-name',
+ location: 'some-location',
+ selfLink: 'http://127.0.0.1/some-link',
+ masterAuth: {
+ clusterCaCertificate: 'abcdefg',
+ },
+ },
+ ],
+ [
+ 'no-location',
+ {
+ name: 'some-name',
+ endpoint: 'http://127.0.0.1:1234',
+ selfLink: 'http://127.0.0.1/some-link',
+ masterAuth: {
+ clusterCaCertificate: 'abcdefg',
+ },
+ },
+ ],
+ ];
+
+ it.each(ignoredPartialClustersTests)(
+ 'ignore cluster - %s',
+ async (_name, ignoredCluster) => {
+ clusterManagerClientMock.listClusters.mockImplementation(req => {
+ if (req.parent === 'parent1') {
+ return [ignoredCluster];
+ }
+ return [
+ {
+ clusters: [],
+ },
+ ];
+ });
+ await gkeEntityProvider.refresh();
+ expect(connectionMock.applyMutation).toHaveBeenCalledWith({
+ type: 'full',
+ entities: [],
+ });
+ },
+ );
+
+ it('should log GKE API errors', async () => {
+ clusterManagerClientMock.listClusters.mockRejectedValue(
+ new Error('some-error'),
+ );
+ await gkeEntityProvider.refresh();
+ expect(connectionMock.applyMutation).toHaveBeenCalledTimes(0);
+ expect(logger.error).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts
new file mode 100644
index 0000000000..0130955737
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts
@@ -0,0 +1,223 @@
+/*
+ * 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 {
+ TaskRunner,
+ readTaskScheduleDefinitionFromConfig,
+} from '@backstage/backend-tasks';
+import {
+ DeferredEntity,
+ EntityProvider,
+ EntityProviderConnection,
+} from '@backstage/plugin-catalog-node';
+
+import { Logger } from 'winston';
+import * as container from '@google-cloud/container';
+import {
+ ANNOTATION_KUBERNETES_API_SERVER,
+ ANNOTATION_KUBERNETES_API_SERVER_CA,
+ ANNOTATION_KUBERNETES_AUTH_PROVIDER,
+} from '@backstage/plugin-kubernetes-common';
+import { Config } from '@backstage/config';
+import { SchedulerService } from '@backstage/backend-plugin-api';
+
+/**
+ * Catalog provider to ingest GKE clusters
+ *
+ * @public
+ */
+export class GkeEntityProvider implements EntityProvider {
+ private readonly logger: Logger;
+ private readonly scheduleFn: () => Promise;
+ private readonly gkeParents: string[];
+ private readonly clusterManagerClient: container.v1.ClusterManagerClient;
+ private connection?: EntityProviderConnection;
+
+ private constructor(
+ logger: Logger,
+ taskRunner: TaskRunner,
+ gkeParents: string[],
+ clusterManagerClient: container.v1.ClusterManagerClient,
+ ) {
+ this.logger = logger;
+ this.scheduleFn = this.createScheduleFn(taskRunner);
+ this.gkeParents = gkeParents;
+ this.clusterManagerClient = clusterManagerClient;
+ }
+
+ public static fromConfig({
+ logger,
+ scheduler,
+ config,
+ }: {
+ logger: Logger;
+ scheduler: SchedulerService;
+ config: Config;
+ }) {
+ return GkeEntityProvider.fromConfigWithClient({
+ logger,
+ scheduler: scheduler,
+ config,
+ clusterManagerClient: new container.v1.ClusterManagerClient(),
+ });
+ }
+
+ public static fromConfigWithClient({
+ logger,
+ scheduler,
+ config,
+ clusterManagerClient,
+ }: {
+ logger: Logger;
+ scheduler: SchedulerService;
+ config: Config;
+ clusterManagerClient: container.v1.ClusterManagerClient;
+ }) {
+ const gkeProviderConfig = config.getConfig('catalog.providers.gcp.gke');
+ const schedule = readTaskScheduleDefinitionFromConfig(
+ gkeProviderConfig.getConfig('schedule'),
+ );
+ return new GkeEntityProvider(
+ logger,
+ scheduler.createScheduledTaskRunner(schedule),
+ gkeProviderConfig.getStringArray('parents'),
+ clusterManagerClient,
+ );
+ }
+
+ getProviderName(): string {
+ return `gcp-gke`;
+ }
+
+ async connect(connection: EntityProviderConnection): Promise {
+ this.connection = connection;
+ await this.scheduleFn();
+ }
+
+ private filterOutUndefinedDeferredEntity(
+ e: DeferredEntity | undefined,
+ ): e is DeferredEntity {
+ return e !== undefined;
+ }
+
+ private filterOutUndefinedCluster(
+ c: container.protos.google.container.v1.ICluster | null | undefined,
+ ): c is container.protos.google.container.v1.ICluster {
+ return c !== undefined && c !== null;
+ }
+
+ private clusterToResource(
+ cluster: container.protos.google.container.v1.ICluster,
+ ): DeferredEntity | undefined {
+ const location = `${this.getProviderName()}:${cluster.location}`;
+
+ if (!cluster.name || !cluster.selfLink || !location || !cluster.endpoint) {
+ this.logger.warn(
+ `ignoring partial cluster, one of name=${cluster.name}, endpoint=${cluster.endpoint}, selfLink=${cluster.selfLink} or location=${cluster.location} is missing`,
+ );
+ return undefined;
+ }
+
+ // TODO fix location type
+ return {
+ locationKey: location,
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Resource',
+ metadata: {
+ annotations: {
+ [ANNOTATION_KUBERNETES_API_SERVER]: cluster.endpoint,
+ [ANNOTATION_KUBERNETES_API_SERVER_CA]:
+ cluster.masterAuth?.clusterCaCertificate || '',
+ [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google',
+ 'backstage.io/managed-by-location': location,
+ 'backstage.io/managed-by-origin-location': location,
+ },
+ name: cluster.name,
+ namespace: 'default',
+ },
+ spec: {
+ type: 'kubernetes-cluster',
+ owner: 'unknown',
+ },
+ },
+ };
+ }
+
+ private createScheduleFn(taskRunner: TaskRunner): () => Promise {
+ return async () => {
+ const taskId = `${this.getProviderName()}:refresh`;
+ return taskRunner.run({
+ id: taskId,
+ fn: async () => {
+ try {
+ await this.refresh();
+ } catch (error) {
+ this.logger.error(error);
+ }
+ },
+ });
+ };
+ }
+
+ private async getClusters(): Promise<
+ container.protos.google.container.v1.ICluster[]
+ > {
+ const clusters = await Promise.all(
+ this.gkeParents.map(async parent => {
+ const request = {
+ parent: parent,
+ };
+ const [response] = await this.clusterManagerClient.listClusters(
+ request,
+ );
+ return response.clusters?.filter(this.filterOutUndefinedCluster) ?? [];
+ }),
+ );
+ return clusters.flat();
+ }
+
+ async refresh() {
+ if (!this.connection) {
+ throw new Error('Not initialized');
+ }
+
+ this.logger.info('Discovering GKE clusters');
+
+ let clusters: container.protos.google.container.v1.ICluster[];
+
+ try {
+ clusters = await this.getClusters();
+ } catch (e) {
+ this.logger.error('error fetching GKE clusters', e);
+ return;
+ }
+ const resources =
+ clusters
+ .map(c => this.clusterToResource(c))
+ .filter(this.filterOutUndefinedDeferredEntity) ?? [];
+
+ this.logger.info(
+ `Ingesting GKE clusters [${resources
+ .map(r => r.entity.metadata.name)
+ .join(', ')}]`,
+ );
+
+ await this.connection.applyMutation({
+ type: 'full',
+ entities: resources,
+ });
+ }
+}
diff --git a/plugins/catalog-backend-module-gcp/src/providers/index.ts b/plugins/catalog-backend-module-gcp/src/providers/index.ts
new file mode 100644
index 0000000000..7846afc3ed
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/src/providers/index.ts
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+export { GkeEntityProvider } from './GkeEntityProvider';
diff --git a/plugins/catalog-backend-module-gcp/src/setupTests.ts b/plugins/catalog-backend-module-gcp/src/setupTests.ts
new file mode 100644
index 0000000000..aa70772592
--- /dev/null
+++ b/plugins/catalog-backend-module-gcp/src/setupTests.ts
@@ -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 {};
diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md
index 09df93f029..3bb53b864b 100644
--- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md
+++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md
@@ -1,5 +1,47 @@
# @backstage/plugin-catalog-backend-module-gerrit
+## 0.1.16
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/integration@1.6.0
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+
+## 0.1.16-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+
+## 0.1.16-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/integration@1.5.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+
## 0.1.16-next.0
### Patch Changes
diff --git a/plugins/catalog-backend-module-gerrit/config.d.ts b/plugins/catalog-backend-module-gerrit/config.d.ts
index d8b48d2967..a246662b0a 100644
--- a/plugins/catalog-backend-module-gerrit/config.d.ts
+++ b/plugins/catalog-backend-module-gerrit/config.d.ts
@@ -25,9 +25,8 @@ export interface Config {
*
* Maps provider id with configuration.
*/
- gerrit?: Record<
- string,
- {
+ gerrit?: {
+ [name: string]: {
/**
* (Required) The host of the Gerrit integration to use.
*/
@@ -42,8 +41,8 @@ export interface Config {
* The branch where the provider will try to find entities. Defaults to "master".
*/
branch?: string;
- }
- >;
+ };
+ };
};
};
}
diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json
index aa0fc95907..99e9ed5e12 100644
--- a/plugins/catalog-backend-module-gerrit/package.json
+++ b/plugins/catalog-backend-module-gerrit/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-gerrit",
- "version": "0.1.16-next.0",
+ "version": "0.1.16",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.test.ts b/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.test.ts
index 0511aa7b4c..9807814f61 100644
--- a/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.test.ts
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-import { getVoidLogger } from '@backstage/backend-common';
-import { coreServices } from '@backstage/backend-plugin-api';
+import {
+ coreServices,
+ createServiceFactory,
+} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
-import { startTestBackend } from '@backstage/backend-test-utils';
-import { ConfigReader } from '@backstage/config';
+import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { Duration } from 'luxon';
import { catalogModuleGerritEntityProvider } from './catalogModuleGerritEntityProvider';
@@ -45,7 +46,7 @@ describe('catalogModuleGerritEntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
- const config = new ConfigReader({
+ const config = {
catalog: {
providers: {
gerrit: {
@@ -70,16 +71,20 @@ describe('catalogModuleGerritEntityProvider', () => {
},
],
},
- });
+ };
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
- services: [
- [coreServices.config, config],
- [coreServices.logger, getVoidLogger()],
- [coreServices.scheduler, scheduler],
+ features: [
+ catalogModuleGerritEntityProvider(),
+ mockServices.rootConfig.factory({ data: config }),
+ mockServices.logger.factory(),
+ createServiceFactory({
+ service: coreServices.scheduler,
+ deps: {},
+ factory: async () => scheduler,
+ }),
],
- features: [catalogModuleGerritEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
diff --git a/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.ts b/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.ts
index 814259fb40..bb29ce6eef 100644
--- a/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.ts
+++ b/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.ts
@@ -32,7 +32,7 @@ export const catalogModuleGerritEntityProvider = createBackendModule({
env.registerInit({
deps: {
catalog: catalogProcessingExtensionPoint,
- config: coreServices.config,
+ config: coreServices.rootConfig,
logger: coreServices.logger,
scheduler: coreServices.scheduler,
},
diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md
index 3743f035c4..4d14c2ce5a 100644
--- a/plugins/catalog-backend-module-github/CHANGELOG.md
+++ b/plugins/catalog-backend-module-github/CHANGELOG.md
@@ -1,5 +1,60 @@
# @backstage/plugin-catalog-backend-module-github
+## 0.3.3
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- 81c231c9c9ee: Fixed a bug where the visibility filter was case sensitive and casting was inconsistent.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/plugin-catalog-backend@1.12.0
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/plugin-events-node@0.2.9
+ - @backstage/integration@1.6.0
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+ - @backstage/plugin-catalog-common@1.0.15
+
+## 0.3.3-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.12.0-next.2
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+ - @backstage/plugin-events-node@0.2.9-next.2
+
+## 0.3.3-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-catalog-backend@1.12.0-next.1
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/plugin-events-node@0.2.9-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/integration@1.5.1
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+ - @backstage/plugin-catalog-common@1.0.15
+
## 0.3.3-next.0
### Patch Changes
diff --git a/plugins/catalog-backend-module-github/config.d.ts b/plugins/catalog-backend-module-github/config.d.ts
index 5a699e745f..9755504ee2 100644
--- a/plugins/catalog-backend-module-github/config.d.ts
+++ b/plugins/catalog-backend-module-github/config.d.ts
@@ -118,9 +118,8 @@ export interface Config {
*/
schedule?: TaskScheduleDefinitionConfig;
}
- | Record<
- string,
- {
+ | {
+ [name: string]: {
/**
* (Optional) The hostname of your GitHub Enterprise instance.
* Default: `github.com`.
@@ -182,8 +181,8 @@ export interface Config {
* (Optional) TaskScheduleDefinition for the refresh.
*/
schedule?: TaskScheduleDefinitionConfig;
- }
- >;
+ };
+ };
};
};
}
diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json
index 2e64d39638..101c0f54f0 100644
--- a/plugins/catalog-backend-module-github/package.json
+++ b/plugins/catalog-backend-module-github/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-github",
"description": "A Backstage catalog backend module that helps integrate towards GitHub",
- "version": "0.3.3-next.0",
+ "version": "0.3.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-github/src/lib/util.ts b/plugins/catalog-backend-module-github/src/lib/util.ts
index bb91e9dd2a..58ae8d6551 100644
--- a/plugins/catalog-backend-module-github/src/lib/util.ts
+++ b/plugins/catalog-backend-module-github/src/lib/util.ts
@@ -107,5 +107,10 @@ export function satisfiesVisibilityFilter(
if (!visibilities.length) {
return true;
}
- return visibilities.includes(visibility);
+ const lowerCaseVisibilities = visibilities.map(v =>
+ v.toLocaleLowerCase('en-US'),
+ );
+ const lowerCaseVisibility = visibility.toLocaleLowerCase('en-US');
+
+ return lowerCaseVisibilities.includes(lowerCaseVisibility);
}
diff --git a/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.test.ts
index 82a5552dba..9e36966eec 100644
--- a/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.test.ts
@@ -14,18 +14,19 @@
* limitations under the License.
*/
-import { getVoidLogger } from '@backstage/backend-common';
-import { coreServices } from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
-import { startTestBackend } from '@backstage/backend-test-utils';
-import { ConfigReader } from '@backstage/config';
+import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { Duration } from 'luxon';
import { catalogModuleGithubEntityProvider } from './catalogModuleGithubEntityProvider';
import { GithubEntityProvider } from '../providers/GithubEntityProvider';
+import {
+ coreServices,
+ createServiceFactory,
+} from '@backstage/backend-plugin-api';
describe('catalogModuleGithubEntityProvider', () => {
it('should register provider at the catalog extension point', async () => {
@@ -45,7 +46,7 @@ describe('catalogModuleGithubEntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
- const config = new ConfigReader({
+ const config = {
catalog: {
providers: {
github: {
@@ -57,16 +58,19 @@ describe('catalogModuleGithubEntityProvider', () => {
},
},
},
- });
+ };
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
- services: [
- [coreServices.config, config],
- [coreServices.logger, getVoidLogger()],
- [coreServices.scheduler, scheduler],
+ features: [
+ catalogModuleGithubEntityProvider(),
+ mockServices.rootConfig.factory({ data: config }),
+ createServiceFactory({
+ service: coreServices.scheduler,
+ deps: {},
+ factory: async () => scheduler,
+ }),
],
- features: [catalogModuleGithubEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
diff --git a/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.ts
index 997da3023b..8e052bd0a0 100644
--- a/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.ts
+++ b/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.ts
@@ -34,7 +34,7 @@ export const catalogModuleGithubEntityProvider = createBackendModule({
env.registerInit({
deps: {
catalog: catalogProcessingExtensionPoint,
- config: coreServices.config,
+ config: coreServices.rootConfig,
logger: coreServices.logger,
scheduler: coreServices.scheduler,
},
diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md
index 2fcba33d47..0f860bb050 100644
--- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md
+++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md
@@ -1,5 +1,53 @@
# @backstage/plugin-catalog-backend-module-gitlab
+## 0.2.4
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- e6c721439f37: Added option to skip forked repos in GitlabDiscoveryEntityProvider
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- 2fe1f5973ff7: Filter Gitlab archived projects through APIs
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/integration@1.6.0
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
+## 0.2.4-next.2
+
+### Patch Changes
+
+- 2fe1f5973ff7: Filter Gitlab archived projects through APIs
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+
+## 0.2.4-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- e6c721439f37: Added option to skip forked repos in GitlabDiscoveryEntityProvider
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/integration@1.5.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
## 0.2.4-next.0
### Patch Changes
diff --git a/plugins/catalog-backend-module-gitlab/config.d.ts b/plugins/catalog-backend-module-gitlab/config.d.ts
index f5afc71093..7d183b6e58 100644
--- a/plugins/catalog-backend-module-gitlab/config.d.ts
+++ b/plugins/catalog-backend-module-gitlab/config.d.ts
@@ -22,9 +22,8 @@ export interface Config {
/**
* GitlabDiscoveryEntityProvider configuration
*/
- gitlab?: Record<
- string,
- {
+ gitlab?: {
+ [name: string]: {
/**
* (Required) Gitlab's host name.
*/
@@ -51,21 +50,21 @@ export interface Config {
/**
* (Optional) RegExp for the Project Name Pattern
*/
- projectPattern?: RegExp;
+ projectPattern?: string;
/**
* (Optional) RegExp for the User Name Pattern
*/
- userPattern?: RegExp;
+ userPattern?: string;
/**
* (Optional) RegExp for the Group Name Pattern
*/
- groupPattern?: RegExp;
+ groupPattern?: string;
/**
* (Optional) Skip forked repository
*/
skipForkedRepos?: boolean;
- }
- >;
+ };
+ };
};
};
}
diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json
index 1a3f0245eb..3f31ffdf1d 100644
--- a/plugins/catalog-backend-module-gitlab/package.json
+++ b/plugins/catalog-backend-module-gitlab/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-gitlab",
"description": "A Backstage catalog backend module that helps integrate towards GitLab",
- "version": "0.2.4-next.0",
+ "version": "0.2.4",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts
index 6333184ea8..a56c229105 100644
--- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts
+++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts
@@ -56,6 +56,7 @@ function setupFakeServer(
listProjectsCallback: (request: {
page: number;
include_subgroups: boolean;
+ archived: boolean;
}) => {
data: GitLabProject[];
nextPage?: number;
@@ -74,20 +75,24 @@ function setupFakeServer(
}
const page = req.url.searchParams.get('page');
const include_subgroups = req.url.searchParams.get('include_subgroups');
+ const archived = req.url.searchParams.get('archived');
const response = listProjectsCallback({
page: parseInt(page!, 10),
include_subgroups: include_subgroups === 'true',
+ archived: archived === 'true',
});
// Filter the fake results based on the `last_activity_after` parameter
const last_activity_after = req.url.searchParams.get(
'last_activity_after',
);
- const filteredData = response.data.filter(
- v =>
- !last_activity_after ||
- Date.parse(v.last_activity_at) >= Date.parse(last_activity_after),
- );
+ const filteredData = response.data
+ .filter(
+ v =>
+ !last_activity_after ||
+ Date.parse(v.last_activity_at) >= Date.parse(last_activity_after),
+ )
+ .filter(v => archived || !v.archived);
return res(
ctx.set('x-next-page', response.nextPage?.toString() ?? ''),
@@ -250,6 +255,7 @@ describe('GitlabDiscoveryProcessor', () => {
},
],
};
+
default:
throw new Error('Invalid request');
}
diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts
index c9bd4d9187..02b1b1cb51 100644
--- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts
+++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts
@@ -109,6 +109,7 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor {
const lastActivity = (await this.cache.get(this.getCacheKey())) as string;
const opts = {
+ archived: false,
group,
page: 1,
// We check for the existence of lastActivity and only set it if it's present to ensure
@@ -125,10 +126,6 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor {
for await (const project of projects) {
res.scanned++;
- if (project.archived) {
- continue;
- }
-
if (branch === '*' && project.default_branch === undefined) {
continue;
}
diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.ts
index f0e169d8df..41b99e1c5b 100644
--- a/plugins/catalog-backend-module-gitlab/src/lib/client.ts
+++ b/plugins/catalog-backend-module-gitlab/src/lib/client.ts
@@ -30,6 +30,7 @@ export type CommonListOptions = {
};
interface ListProjectOptions extends CommonListOptions {
+ archived?: boolean;
group?: string;
}
diff --git a/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts
index 7a3d0a0e0d..22759cafa0 100644
--- a/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-import { getVoidLogger } from '@backstage/backend-common';
-import { coreServices } from '@backstage/backend-plugin-api';
+import {
+ coreServices,
+ createServiceFactory,
+} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
-import { startTestBackend } from '@backstage/backend-test-utils';
-import { ConfigReader } from '@backstage/config';
+import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { Duration } from 'luxon';
import { catalogModuleGitlabDiscoveryEntityProvider } from './catalogModuleGitlabDiscoveryEntityProvider';
@@ -45,7 +46,7 @@ describe('catalogModuleGitlabDiscoveryEntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
- const config = new ConfigReader({
+ const config = {
integrations: {
gitlab: [
{
@@ -69,16 +70,20 @@ describe('catalogModuleGitlabDiscoveryEntityProvider', () => {
},
},
},
- });
+ };
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
- services: [
- [coreServices.config, config],
- [coreServices.logger, getVoidLogger()],
- [coreServices.scheduler, scheduler],
+ features: [
+ catalogModuleGitlabDiscoveryEntityProvider(),
+ mockServices.rootConfig.factory({ data: config }),
+ mockServices.logger.factory(),
+ createServiceFactory({
+ deps: {},
+ service: coreServices.scheduler,
+ factory: async () => scheduler,
+ }),
],
- features: [catalogModuleGitlabDiscoveryEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M'));
diff --git a/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.ts
index ffae233319..d192e8f3a5 100644
--- a/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.ts
+++ b/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.ts
@@ -33,7 +33,7 @@ export const catalogModuleGitlabDiscoveryEntityProvider = createBackendModule({
register(env) {
env.registerInit({
deps: {
- config: coreServices.config,
+ config: coreServices.rootConfig,
catalog: catalogProcessingExtensionPoint,
logger: coreServices.logger,
scheduler: coreServices.scheduler,
diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts
index 08d2fe48a7..ce7a304746 100644
--- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts
+++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts
@@ -155,6 +155,7 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider {
const projects = paginated(
options => client.listProjects(options),
{
+ archived: false,
group: this.config.group,
page: 1,
per_page: 50,
@@ -173,10 +174,6 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider {
res.scanned++;
- if (project.archived) {
- continue;
- }
-
if (
this.config.skipForkedRepos &&
project.hasOwnProperty('forked_from_project')
diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md
index 583458667d..7fa5ac3c11 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md
+++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md
@@ -1,5 +1,52 @@
# @backstage/plugin-catalog-backend-module-incremental-ingestion
+## 0.4.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- e2b6396a1274: Export new alpha `incrementalIngestionProvidersExtensionPoint` for registering incremental providers, rather than the providers being passed as options to the backend module.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/plugin-catalog-backend@1.12.0
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/plugin-events-node@0.2.9
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/plugin-permission-common@0.7.7
+
+## 0.4.1-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.12.0-next.2
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+ - @backstage/plugin-events-node@0.2.9-next.2
+
+## 0.4.1-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-catalog-backend@1.12.0-next.1
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/plugin-events-node@0.2.9-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/plugin-permission-common@0.7.7
+
## 0.4.1-next.0
### Patch Changes
diff --git a/plugins/catalog-backend-module-incremental-ingestion/alpha-api-report.md b/plugins/catalog-backend-module-incremental-ingestion/alpha-api-report.md
index b11ec43cf5..995c6e4893 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/alpha-api-report.md
+++ b/plugins/catalog-backend-module-incremental-ingestion/alpha-api-report.md
@@ -4,16 +4,23 @@
```ts
import { BackendFeature } from '@backstage/backend-plugin-api';
+import { ExtensionPoint } from '@backstage/backend-plugin-api';
import { IncrementalEntityProvider } from '@backstage/plugin-catalog-backend-module-incremental-ingestion';
import { IncrementalEntityProviderOptions } from '@backstage/plugin-catalog-backend-module-incremental-ingestion';
// @alpha
-export const catalogModuleIncrementalIngestionEntityProvider: (options: {
- providers: {
- provider: IncrementalEntityProvider;
+export const catalogModuleIncrementalIngestionEntityProvider: () => BackendFeature;
+
+// @alpha
+export interface IncrementalIngestionProviderExtensionPoint {
+ addProvider(config: {
options: IncrementalEntityProviderOptions;
- }[];
-}) => BackendFeature;
+ provider: IncrementalEntityProvider;
+ }): void;
+}
+
+// @alpha
+export const incrementalIngestionProvidersExtensionPoint: ExtensionPoint;
// (No @packageDocumentation comment for this package)
```
diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json
index a6a947c93c..30a64c20b2 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/package.json
+++ b/plugins/catalog-backend-module-incremental-ingestion/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-incremental-ingestion",
"description": "An entity provider for streaming large asset sources into the catalog",
- "version": "0.4.1-next.0",
+ "version": "0.4.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.ts
index 4738893c95..cc2954ea05 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.ts
+++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.ts
@@ -15,7 +15,7 @@
*/
import {
- ConfigService,
+ RootConfigService,
LoggerService,
SchedulerService,
} from '@backstage/backend-plugin-api';
@@ -49,7 +49,7 @@ export class WrapperProviders {
constructor(
private readonly options: {
- config: ConfigService;
+ config: RootConfigService;
logger: LoggerService;
client: Knex;
scheduler: SchedulerService;
diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.test.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.test.ts
index ef07b4cb10..ed0c776fa8 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.test.ts
@@ -14,13 +14,18 @@
* limitations under the License.
*/
-import { getVoidLogger } from '@backstage/backend-common';
-import { coreServices } from '@backstage/backend-plugin-api';
+import {
+ coreServices,
+ createBackendModule,
+ createServiceFactory,
+} from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
-import { ConfigReader } from '@backstage/config';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { IncrementalEntityProvider } from '../types';
-import { catalogModuleIncrementalIngestionEntityProvider } from './catalogModuleIncrementalIngestionEntityProvider';
+import {
+ catalogModuleIncrementalIngestionEntityProvider,
+ incrementalIngestionProvidersExtensionPoint,
+} from './catalogModuleIncrementalIngestionEntityProvider';
describe('catalogModuleIncrementalIngestionEntityProvider', () => {
it('should register provider at the catalog extension point', async () => {
@@ -37,37 +42,35 @@ describe('catalogModuleIncrementalIngestionEntityProvider', () => {
const addEntityProvider = jest.fn();
const httpRouterUse = jest.fn();
- const scheduler = {};
- const database = {
- getClient: jest.fn(),
- };
- const httpRouter = {
- use: httpRouterUse,
- };
-
await startTestBackend({
extensionPoints: [
[catalogProcessingExtensionPoint, { addEntityProvider }],
],
- services: [
- [coreServices.config, new ConfigReader({})],
- [coreServices.database, database],
- [coreServices.httpRouter, httpRouter],
- [coreServices.logger, getVoidLogger()],
- [coreServices.scheduler, scheduler],
- ],
features: [
- catalogModuleIncrementalIngestionEntityProvider({
- providers: [
- {
- provider: provider1,
- options: {
- burstInterval: { seconds: 1 },
- burstLength: { seconds: 1 },
- restLength: { seconds: 1 },
+ createServiceFactory({
+ service: coreServices.httpRouter,
+ deps: {},
+ factory: () => ({ use: httpRouterUse }),
+ }),
+ catalogModuleIncrementalIngestionEntityProvider(),
+ createBackendModule({
+ pluginId: 'catalog',
+ moduleId: 'incrementalTest',
+ register(env) {
+ env.registerInit({
+ deps: { extension: incrementalIngestionProvidersExtensionPoint },
+ async init({ extension }) {
+ extension.addProvider({
+ provider: provider1,
+ options: {
+ burstInterval: { seconds: 1 },
+ burstLength: { seconds: 1 },
+ restLength: { seconds: 1 },
+ },
+ });
},
- },
- ],
+ });
+ },
}),
],
});
diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.ts
index 19d5742b46..4e94088ef6 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.ts
+++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.ts
@@ -17,6 +17,7 @@
import {
coreServices,
createBackendModule,
+ createExtensionPoint,
} from '@backstage/backend-plugin-api';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import {
@@ -25,56 +26,111 @@ import {
} from '@backstage/plugin-catalog-backend-module-incremental-ingestion';
import { WrapperProviders } from './WrapperProviders';
+/**
+ * @alpha
+ * Interface for {@link incrementalIngestionProvidersExtensionPoint}.
+ */
+export interface IncrementalIngestionProviderExtensionPoint {
+ /** Adds a new incremental entity provider */
+ addProvider(config: {
+ options: IncrementalEntityProviderOptions;
+ provider: IncrementalEntityProvider;
+ }): void;
+}
+
+/**
+ * @alpha
+ *
+ * Extension point for registering incremental ingestion providers.
+ * The `catalogModuleIncrementalIngestionEntityProvider` must be installed for these providers to work.
+ *
+ * @example
+ *
+ * ```ts
+ * backend.add(createBackendModule({
+ * pluginId: 'catalog',
+ * moduleId: 'myIncrementalProvider',
+ * register(env) {
+ * env.registerInit({
+ * deps: {
+ * extension: incrementalIngestionProvidersExtensionPoint,
+ * },
+ * async init({ extension }) {
+ * extension.addProvider({
+ * burstInterval: ...,
+ * burstLength: ...,
+ * restLength: ...,
+ * }, {
+ * next(context, cursor) {
+ * ...
+ * },
+ * ...
+ * })
+ * })
+ * })
+ * }
+ * }))
+ * ```
+ */
+export const incrementalIngestionProvidersExtensionPoint =
+ createExtensionPoint({
+ id: 'catalog.incrementalIngestionProvider.providers',
+ });
+
/**
* Registers the incremental entity provider with the catalog processing extension point.
*
* @alpha
*/
export const catalogModuleIncrementalIngestionEntityProvider =
- createBackendModule(
- (options: {
- providers: Array<{
+ createBackendModule({
+ pluginId: 'catalog',
+ moduleId: 'incrementalIngestionEntityProvider',
+ register(env) {
+ const addedProviders = new Array<{
provider: IncrementalEntityProvider;
options: IncrementalEntityProviderOptions;
- }>;
- }) => ({
- pluginId: 'catalog',
- moduleId: 'incrementalIngestionEntityProvider',
- register(env) {
- env.registerInit({
- deps: {
- catalog: catalogProcessingExtensionPoint,
- config: coreServices.config,
- database: coreServices.database,
- httpRouter: coreServices.httpRouter,
- logger: coreServices.logger,
- scheduler: coreServices.scheduler,
- },
- async init({
- catalog,
+ }>();
+
+ env.registerExtensionPoint(incrementalIngestionProvidersExtensionPoint, {
+ addProvider({ options, provider }) {
+ addedProviders.push({ options, provider });
+ },
+ });
+
+ env.registerInit({
+ deps: {
+ catalog: catalogProcessingExtensionPoint,
+ config: coreServices.rootConfig,
+ database: coreServices.database,
+ httpRouter: coreServices.httpRouter,
+ logger: coreServices.logger,
+ scheduler: coreServices.scheduler,
+ },
+ async init({
+ catalog,
+ config,
+ database,
+ httpRouter,
+ logger,
+ scheduler,
+ }) {
+ const client = await database.getClient();
+
+ const providers = new WrapperProviders({
config,
- database,
- httpRouter,
logger,
+ client,
scheduler,
- }) {
- const client = await database.getClient();
+ });
- const providers = new WrapperProviders({
- config,
- logger,
- client,
- scheduler,
- });
+ for (const entry of addedProviders) {
+ const wrapped = providers.wrap(entry.provider, entry.options);
+ catalog.addEntityProvider(wrapped);
+ }
- for (const entry of options.providers) {
- const wrapped = providers.wrap(entry.provider, entry.options);
- catalog.addEntityProvider(wrapped);
- }
-
- httpRouter.use(await providers.adminRouter());
- },
- });
- },
- }),
- );
+ httpRouter.use(await providers.adminRouter());
+ },
+ });
+ },
+ });
diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/index.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/index.ts
index 9fcee99e01..37ecf60f7c 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/src/module/index.ts
+++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/index.ts
@@ -14,4 +14,8 @@
* limitations under the License.
*/
-export { catalogModuleIncrementalIngestionEntityProvider } from './catalogModuleIncrementalIngestionEntityProvider';
+export {
+ catalogModuleIncrementalIngestionEntityProvider,
+ incrementalIngestionProvidersExtensionPoint,
+ type IncrementalIngestionProviderExtensionPoint,
+} from './catalogModuleIncrementalIngestionEntityProvider';
diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/run.ts b/plugins/catalog-backend-module-incremental-ingestion/src/run.ts
index f94c988237..cfb4c20def 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/src/run.ts
+++ b/plugins/catalog-backend-module-incremental-ingestion/src/run.ts
@@ -20,12 +20,16 @@
import { createBackend } from '@backstage/backend-defaults';
import {
coreServices,
+ createBackendModule,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { ConfigReader } from '@backstage/config';
import { catalogPlugin } from '@backstage/plugin-catalog-backend/alpha';
import { IncrementalEntityProvider } from '.';
-import { catalogModuleIncrementalIngestionEntityProvider } from './alpha';
+import {
+ catalogModuleIncrementalIngestionEntityProvider,
+ incrementalIngestionProvidersExtensionPoint,
+} from './alpha';
const provider: IncrementalEntityProvider = {
getProviderName: () => 'test-provider',
@@ -51,29 +55,36 @@ async function main() {
},
};
- const backend = createBackend({
- services: [
- createServiceFactory({
- service: coreServices.config,
- deps: {},
- factory: () => new ConfigReader(config),
- }),
- ],
- });
+ const backend = createBackend();
- backend.add(catalogPlugin());
backend.add(
- catalogModuleIncrementalIngestionEntityProvider({
- providers: [
- {
- provider: provider,
- options: {
- burstInterval: { seconds: 1 },
- burstLength: { seconds: 10 },
- restLength: { seconds: 10 },
+ createServiceFactory({
+ service: coreServices.rootConfig,
+ deps: {},
+ factory: () => new ConfigReader(config),
+ }),
+ );
+ backend.add(catalogPlugin());
+ backend.add(catalogModuleIncrementalIngestionEntityProvider());
+ backend.add(
+ createBackendModule({
+ pluginId: 'catalog',
+ moduleId: 'incrementalTestProvider',
+ register(reg) {
+ reg.registerInit({
+ deps: { extension: incrementalIngestionProvidersExtensionPoint },
+ async init({ extension }) {
+ extension.addProvider({
+ provider: provider,
+ options: {
+ burstInterval: { seconds: 1 },
+ burstLength: { seconds: 10 },
+ restLength: { seconds: 10 },
+ },
+ });
},
- },
- ],
+ });
+ },
}),
);
diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md
index 4e9cabb577..9db8a14c9f 100644
--- a/plugins/catalog-backend-module-ldap/CHANGELOG.md
+++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md
@@ -1,5 +1,39 @@
# @backstage/plugin-catalog-backend-module-ldap
+## 0.5.15
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+ - @backstage/plugin-catalog-common@1.0.15
+
+## 0.5.15-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+
+## 0.5.15-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+ - @backstage/plugin-catalog-common@1.0.15
+
## 0.5.15-next.0
### Patch Changes
diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json
index 742065735d..8d3a6277c3 100644
--- a/plugins/catalog-backend-module-ldap/package.json
+++ b/plugins/catalog-backend-module-ldap/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-ldap",
"description": "A Backstage catalog backend module that helps integrate towards LDAP",
- "version": "0.5.15-next.0",
+ "version": "0.5.15",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md
index 0edda3b91d..383ceffe0e 100644
--- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md
+++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md
@@ -1,5 +1,46 @@
# @backstage/plugin-catalog-backend-module-msgraph
+## 0.5.7
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- fb93323201bf: The alpha `catalogModuleMicrosoftGraphOrgEntityProvider` export no longer accepts options. Transformers are now instead configured via the `microsoftGraphOrgEntityProviderTransformExtensionPoint`.
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/plugin-catalog-common@1.0.15
+
+## 0.5.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+
+## 0.5.7-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/plugin-catalog-common@1.0.15
+
## 0.5.7-next.0
### Patch Changes
diff --git a/plugins/catalog-backend-module-msgraph/alpha-api-report.md b/plugins/catalog-backend-module-msgraph/alpha-api-report.md
index 0fa093a1bc..6d877ba713 100644
--- a/plugins/catalog-backend-module-msgraph/alpha-api-report.md
+++ b/plugins/catalog-backend-module-msgraph/alpha-api-report.md
@@ -4,6 +4,7 @@
```ts
import { BackendFeature } from '@backstage/backend-plugin-api';
+import { ExtensionPoint } from '@backstage/backend-plugin-api';
import { GroupTransformer } from '@backstage/plugin-catalog-backend-module-msgraph';
import { OrganizationTransformer } from '@backstage/plugin-catalog-backend-module-msgraph';
import { UserTransformer } from '@backstage/plugin-catalog-backend-module-msgraph';
@@ -12,12 +13,21 @@ import { UserTransformer } from '@backstage/plugin-catalog-backend-module-msgrap
export const catalogModuleMicrosoftGraphOrgEntityProvider: () => BackendFeature;
// @alpha
-export interface CatalogModuleMicrosoftGraphOrgEntityProviderOptions {
- groupTransformer?: GroupTransformer | Record;
- organizationTransformer?:
- | OrganizationTransformer
- | Record;
- userTransformer?: UserTransformer | Record;
+export const microsoftGraphOrgEntityProviderTransformExtensionPoint: ExtensionPoint;
+
+// @alpha
+export interface MicrosoftGraphOrgEntityProviderTransformsExtensionPoint {
+ setGroupTransformer(
+ transformer: GroupTransformer | Record,
+ ): void;
+ setOrganizationTransformer(
+ transformer:
+ | OrganizationTransformer
+ | Record,
+ ): void;
+ setUserTransformer(
+ transformer: UserTransformer | Record,
+ ): void;
}
// (No @packageDocumentation comment for this package)
diff --git a/plugins/catalog-backend-module-msgraph/config.d.ts b/plugins/catalog-backend-module-msgraph/config.d.ts
index 904ce1f646..9d341215cd 100644
--- a/plugins/catalog-backend-module-msgraph/config.d.ts
+++ b/plugins/catalog-backend-module-msgraph/config.d.ts
@@ -209,9 +209,8 @@ export interface Config {
*/
schedule?: TaskScheduleDefinitionConfig;
}
- | Record<
- string,
- {
+ | {
+ [name: string]: {
/**
* The prefix of the target that this matches on, e.g.
* "https://graph.microsoft.com/v1.0", with no trailing slash.
@@ -296,8 +295,8 @@ export interface Config {
* (Optional) TaskScheduleDefinition for the refresh.
*/
schedule?: TaskScheduleDefinitionConfig;
- }
- >;
+ };
+ };
};
};
}
diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json
index c802b15d16..5d2afe69bf 100644
--- a/plugins/catalog-backend-module-msgraph/package.json
+++ b/plugins/catalog-backend-module-msgraph/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-msgraph",
"description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph",
- "version": "0.5.7-next.0",
+ "version": "0.5.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.test.ts b/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.test.ts
index 3362c75c95..96fec41cbe 100644
--- a/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.test.ts
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-import { getVoidLogger } from '@backstage/backend-common';
-import { coreServices } from '@backstage/backend-plugin-api';
+import {
+ coreServices,
+ createServiceFactory,
+} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
-import { startTestBackend } from '@backstage/backend-test-utils';
-import { ConfigReader } from '@backstage/config';
+import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { Duration } from 'luxon';
import { catalogModuleMicrosoftGraphOrgEntityProvider } from './catalogModuleMicrosoftGraphOrgEntityProvider';
@@ -45,7 +46,7 @@ describe('catalogModuleMicrosoftGraphOrgEntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
- const config = new ConfigReader({
+ const config = {
catalog: {
providers: {
microsoftGraphOrg: {
@@ -62,16 +63,19 @@ describe('catalogModuleMicrosoftGraphOrgEntityProvider', () => {
},
},
},
- });
+ };
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
- services: [
- [coreServices.config, config],
- [coreServices.logger, getVoidLogger()],
- [coreServices.scheduler, scheduler],
+ features: [
+ catalogModuleMicrosoftGraphOrgEntityProvider(),
+ mockServices.rootConfig.factory({ data: config }),
+ createServiceFactory(() => ({
+ deps: {},
+ service: coreServices.scheduler,
+ factory: async () => scheduler,
+ })),
],
- features: [catalogModuleMicrosoftGraphOrgEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('PT30M'));
diff --git a/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.ts
index 7cd8b65f0b..b0d22ab0f4 100644
--- a/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.ts
+++ b/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.ts
@@ -17,6 +17,7 @@
import {
coreServices,
createBackendModule,
+ createExtensionPoint,
} from '@backstage/backend-plugin-api';
import { loggerToWinstonLogger } from '@backstage/backend-common';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
@@ -28,32 +29,50 @@ import {
import { MicrosoftGraphOrgEntityProvider } from '../processors';
/**
- * Options for {@link catalogModuleMicrosoftGraphOrgEntityProvider}.
+ * Interface for {@link microsoftGraphOrgEntityProviderTransformExtensionPoint}.
*
* @alpha
*/
-export interface CatalogModuleMicrosoftGraphOrgEntityProviderOptions {
+export interface MicrosoftGraphOrgEntityProviderTransformsExtensionPoint {
/**
- * The function that transforms a user entry in msgraph to an entity.
+ * Set the function that transforms a user entry in msgraph to an entity.
* Optionally, you can pass separate transformers per provider ID.
*/
- userTransformer?: UserTransformer | Record;
+ setUserTransformer(
+ transformer: UserTransformer | Record,
+ ): void;
/**
- * The function that transforms a group entry in msgraph to an entity.
+ * Set the function that transforms a group entry in msgraph to an entity.
* Optionally, you can pass separate transformers per provider ID.
*/
- groupTransformer?: GroupTransformer | Record;
+ setGroupTransformer(
+ transformer: GroupTransformer | Record,
+ ): void;
/**
- * The function that transforms an organization entry in msgraph to an entity.
+ * Set the function that transforms an organization entry in msgraph to an entity.
* Optionally, you can pass separate transformers per provider ID.
*/
- organizationTransformer?:
- | OrganizationTransformer
- | Record;
+ setOrganizationTransformer(
+ transformer:
+ | OrganizationTransformer
+ | Record,
+ ): void;
}
+/**
+ * Extension point used to customize the transforms used by the {@link catalogModuleMicrosoftGraphOrgEntityProvider}.
+ *
+ * @alpha
+ */
+export const microsoftGraphOrgEntityProviderTransformExtensionPoint =
+ createExtensionPoint(
+ {
+ id: 'catalog.microsoftGraphOrgEntityProvider.transforms',
+ },
+ );
+
/**
* Registers the MicrosoftGraphOrgEntityProvider with the catalog processing extension point.
*
@@ -63,25 +82,59 @@ export const catalogModuleMicrosoftGraphOrgEntityProvider = createBackendModule(
{
pluginId: 'catalog',
moduleId: 'microsoftGraphOrgEntityProvider',
- register(
- env,
- options?: CatalogModuleMicrosoftGraphOrgEntityProviderOptions,
- ) {
+ register(env) {
+ let userTransformer:
+ | UserTransformer
+ | Record
+ | undefined;
+ let groupTransformer:
+ | GroupTransformer
+ | Record
+ | undefined;
+ let organizationTransformer:
+ | OrganizationTransformer
+ | Record
+ | undefined;
+
+ env.registerExtensionPoint(
+ microsoftGraphOrgEntityProviderTransformExtensionPoint,
+ {
+ setUserTransformer(transformer) {
+ if (userTransformer) {
+ throw new Error('User transformer may only be set once');
+ }
+ userTransformer = transformer;
+ },
+ setGroupTransformer(transformer) {
+ if (groupTransformer) {
+ throw new Error('Group transformer may only be set once');
+ }
+ groupTransformer = transformer;
+ },
+ setOrganizationTransformer(transformer) {
+ if (organizationTransformer) {
+ throw new Error('Organization transformer may only be set once');
+ }
+ organizationTransformer = transformer;
+ },
+ },
+ );
+
env.registerInit({
deps: {
catalog: catalogProcessingExtensionPoint,
- config: coreServices.config,
+ config: coreServices.rootConfig,
logger: coreServices.logger,
scheduler: coreServices.scheduler,
},
async init({ catalog, config, logger, scheduler }) {
catalog.addEntityProvider(
MicrosoftGraphOrgEntityProvider.fromConfig(config, {
- groupTransformer: options?.groupTransformer,
logger: loggerToWinstonLogger(logger),
- organizationTransformer: options?.organizationTransformer,
scheduler,
- userTransformer: options?.userTransformer,
+ userTransformer: userTransformer,
+ groupTransformer: groupTransformer,
+ organizationTransformer: organizationTransformer,
}),
);
},
diff --git a/plugins/catalog-backend-module-msgraph/src/module/index.ts b/plugins/catalog-backend-module-msgraph/src/module/index.ts
index f53f41141c..ef45c6a135 100644
--- a/plugins/catalog-backend-module-msgraph/src/module/index.ts
+++ b/plugins/catalog-backend-module-msgraph/src/module/index.ts
@@ -14,5 +14,8 @@
* limitations under the License.
*/
-export { catalogModuleMicrosoftGraphOrgEntityProvider } from './catalogModuleMicrosoftGraphOrgEntityProvider';
-export type { CatalogModuleMicrosoftGraphOrgEntityProviderOptions } from './catalogModuleMicrosoftGraphOrgEntityProvider';
+export {
+ catalogModuleMicrosoftGraphOrgEntityProvider,
+ microsoftGraphOrgEntityProviderTransformExtensionPoint,
+ type MicrosoftGraphOrgEntityProviderTransformsExtensionPoint,
+} from './catalogModuleMicrosoftGraphOrgEntityProvider';
diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md
index 8b6a4c3162..7a7bf51087 100644
--- a/plugins/catalog-backend-module-openapi/CHANGELOG.md
+++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md
@@ -1,5 +1,42 @@
# @backstage/plugin-catalog-backend-module-openapi
+## 0.1.14
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/plugin-catalog-backend@1.12.0
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/integration@1.6.0
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/types@1.1.0
+ - @backstage/plugin-catalog-common@1.0.15
+
+## 0.1.14-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.12.0-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+
+## 0.1.14-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-catalog-backend@1.12.0-next.1
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/integration@1.5.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/types@1.1.0
+ - @backstage/plugin-catalog-common@1.0.15
+
## 0.1.14-next.0
### Patch Changes
diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json
index cbc42851d9..f9c5fe88a1 100644
--- a/plugins/catalog-backend-module-openapi/package.json
+++ b/plugins/catalog-backend-module-openapi/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-openapi",
"description": "A Backstage catalog backend module that helps with OpenAPI specifications",
- "version": "0.1.14-next.0",
+ "version": "0.1.14",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md
index d7606911e3..31fbe07360 100644
--- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md
+++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md
@@ -1,5 +1,47 @@
# @backstage/plugin-catalog-backend-module-puppetdb
+## 0.1.5
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
+## 0.1.5-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+
+## 0.1.5-next.1
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- 4b82382ed8c2: Fixed invalid configuration schema. The configuration schema may be more strict as a result.
+- Updated dependencies
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+
## 0.1.5-next.0
### Patch Changes
diff --git a/plugins/catalog-backend-module-puppetdb/config.d.ts b/plugins/catalog-backend-module-puppetdb/config.d.ts
index edd7ab3889..8aab239047 100644
--- a/plugins/catalog-backend-module-puppetdb/config.d.ts
+++ b/plugins/catalog-backend-module-puppetdb/config.d.ts
@@ -46,9 +46,8 @@ export interface Config {
*/
schedule?: TaskScheduleDefinition;
}
- | Record<
- string,
- {
+ | {
+ [name: string]: {
/**
* (Required) The base URL of PuppetDB API instance.
*/
@@ -61,8 +60,8 @@ export interface Config {
* (Optional) Task schedule definition for the refresh.
*/
schedule?: TaskScheduleDefinition;
- }
- >;
+ };
+ };
};
};
}
diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json
index 8adbd65941..9cd16fd3c3 100644
--- a/plugins/catalog-backend-module-puppetdb/package.json
+++ b/plugins/catalog-backend-module-puppetdb/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-puppetdb",
"description": "A Backstage catalog backend module that helps integrate towards PuppetDB",
- "version": "0.1.5-next.0",
+ "version": "0.1.5",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.test.ts b/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.test.ts
index e2745e9268..3ea54d8c8f 100644
--- a/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.test.ts
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-import { getVoidLogger } from '@backstage/backend-common';
-import { coreServices } from '@backstage/backend-plugin-api';
+import {
+ coreServices,
+ createServiceFactory,
+} from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
-import { startTestBackend } from '@backstage/backend-test-utils';
-import { ConfigReader } from '@backstage/config';
+import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { catalogModulePuppetDbEntityProvider } from './catalogModulePuppetDbEntityProvider';
import { PuppetDbEntityProvider } from '../providers/PuppetDbEntityProvider';
@@ -44,7 +45,7 @@ describe('catalogModulePuppetDbEntityProvider', () => {
},
} as unknown as PluginTaskScheduler;
- const config = new ConfigReader({
+ const config = {
catalog: {
providers: {
puppetdb: {
@@ -56,16 +57,19 @@ describe('catalogModulePuppetDbEntityProvider', () => {
},
},
},
- });
+ };
await startTestBackend({
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
- services: [
- [coreServices.config, config],
- [coreServices.logger, getVoidLogger()],
- [coreServices.scheduler, scheduler],
+ features: [
+ catalogModulePuppetDbEntityProvider(),
+ mockServices.rootConfig.factory({ data: config }),
+ createServiceFactory(() => ({
+ deps: {},
+ service: coreServices.scheduler,
+ factory: async () => scheduler,
+ })),
],
- features: [catalogModulePuppetDbEntityProvider()],
});
expect(usedSchedule?.frequency).toEqual({ minutes: 10 });
diff --git a/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.ts b/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.ts
index 9a7d279cd3..5546b5283b 100644
--- a/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.ts
+++ b/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.ts
@@ -34,7 +34,7 @@ export const catalogModulePuppetDbEntityProvider = createBackendModule({
env.registerInit({
deps: {
catalog: catalogProcessingExtensionPoint,
- config: coreServices.config,
+ config: coreServices.rootConfig,
logger: coreServices.logger,
scheduler: coreServices.scheduler,
},
diff --git a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md
index 999c7cf85f..96228acefd 100644
--- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md
+++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md
@@ -1,5 +1,37 @@
# @backstage/plugin-catalog-backend-module-unprocessed
+## 0.2.0
+
+### Minor Changes
+
+- 5156a94c2e2a: **BREAKING**: Fixing typo in exported module. You will have to rename the import to the correct spelling. `UnprocessedEntites` -> `UnprocessedEntities`
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/plugin-auth-node@0.2.17
+ - @backstage/catalog-model@1.4.1
+
+## 0.2.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/plugin-auth-node@0.2.17-next.2
+
+## 0.2.0-next.1
+
+### Patch Changes
+
+- 12a8c94eda8d: Add package repository and homepage metadata
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.2.17-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/catalog-model@1.4.1
+
## 0.2.0-next.0
### Minor Changes
diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json
index 9f3f2f0b11..8f303b9667 100644
--- a/plugins/catalog-backend-module-unprocessed/package.json
+++ b/plugins/catalog-backend-module-unprocessed/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-unprocessed",
"description": "Backstage Catalog module to view unprocessed entities",
- "version": "0.2.0-next.0",
+ "version": "0.2.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -13,6 +13,12 @@
"backstage": {
"role": "backend-plugin-module"
},
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/catalog-backend-module-unprocessed"
+ },
"scripts": {
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md
index 0c0f3c7be8..e1fd4e43ab 100644
--- a/plugins/catalog-backend/CHANGELOG.md
+++ b/plugins/catalog-backend/CHANGELOG.md
@@ -1,5 +1,86 @@
# @backstage/plugin-catalog-backend
+## 1.12.0
+
+### Minor Changes
+
+- b8cccd8ee858: Support configuring applicable kinds for `AnnotateScmSlugEntityProcessor`
+- f32252cdf631: Added OpenTelemetry spans for catalog processing
+- ebeb77586975: Now performs request validation based on OpenAPI schema through `@backstage/backend-openapi-utils`. Error responses for invalid input, like `"a"` instead of a number, may have changed.
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- b8d6b22acd57: Internal refactor for load test
+- Updated dependencies
+ - @backstage/plugin-search-backend-module-catalog@0.1.4
+ - @backstage/backend-common@0.19.2
+ - @backstage/backend-plugin-api@0.6.0
+ - @backstage/backend-openapi-utils@0.0.3
+ - @backstage/plugin-catalog-node@1.4.1
+ - @backstage/plugin-events-node@0.2.9
+ - @backstage/plugin-auth-node@0.2.17
+ - @backstage/integration@1.6.0
+ - @backstage/backend-tasks@0.5.5
+ - @backstage/plugin-scaffolder-common@1.4.0
+ - @backstage/plugin-permission-node@0.7.11
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+ - @backstage/plugin-catalog-common@1.0.15
+ - @backstage/plugin-permission-common@0.7.7
+ - @backstage/plugin-search-common@1.2.5
+
+## 1.12.0-next.2
+
+### Minor Changes
+
+- b8cccd8ee858: Support configuring applicable kinds for `AnnotateScmSlugEntityProcessor`
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-search-backend-module-catalog@0.1.4-next.2
+ - @backstage/backend-plugin-api@0.6.0-next.2
+ - @backstage/backend-tasks@0.5.5-next.2
+ - @backstage/backend-common@0.19.2-next.2
+ - @backstage/plugin-catalog-node@1.4.1-next.2
+ - @backstage/plugin-events-node@0.2.9-next.2
+ - @backstage/plugin-permission-node@0.7.11-next.2
+ - @backstage/plugin-auth-node@0.2.17-next.2
+
+## 1.12.0-next.1
+
+### Minor Changes
+
+- f32252cdf631: Added OpenTelemetry spans for catalog processing
+
+### Patch Changes
+
+- 629cbd194a87: Use `coreServices.rootConfig` instead of `coreService.config`
+- Updated dependencies
+ - @backstage/plugin-search-backend-module-catalog@0.1.4-next.1
+ - @backstage/backend-common@0.19.2-next.1
+ - @backstage/backend-openapi-utils@0.0.3-next.1
+ - @backstage/plugin-catalog-node@1.4.1-next.1
+ - @backstage/plugin-events-node@0.2.9-next.1
+ - @backstage/plugin-auth-node@0.2.17-next.1
+ - @backstage/backend-plugin-api@0.6.0-next.1
+ - @backstage/backend-tasks@0.5.5-next.1
+ - @backstage/plugin-permission-node@0.7.11-next.1
+ - @backstage/integration@1.5.1
+ - @backstage/catalog-client@1.4.3
+ - @backstage/catalog-model@1.4.1
+ - @backstage/config@1.0.8
+ - @backstage/errors@1.2.1
+ - @backstage/types@1.1.0
+ - @backstage/plugin-catalog-common@1.0.15
+ - @backstage/plugin-permission-common@0.7.7
+ - @backstage/plugin-scaffolder-common@1.3.2
+ - @backstage/plugin-search-common@1.2.5
+
## 1.12.0-next.0
### Minor Changes
diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md
index 84d8de4753..345874d919 100644
--- a/plugins/catalog-backend/api-report.md
+++ b/plugins/catalog-backend/api-report.md
@@ -91,9 +91,17 @@ export class AnnotateLocationEntityProcessor implements CatalogProcessor_2 {
// @public (undocumented)
export class AnnotateScmSlugEntityProcessor implements CatalogProcessor_2 {
- constructor(opts: { scmIntegrationRegistry: ScmIntegrationRegistry });
+ constructor(opts: {
+ scmIntegrationRegistry: ScmIntegrationRegistry;
+ kinds?: string[];
+ });
// (undocumented)
- static fromConfig(config: Config): AnnotateScmSlugEntityProcessor;
+ static fromConfig(
+ config: Config,
+ options?: {
+ kinds?: string[];
+ },
+ ): AnnotateScmSlugEntityProcessor;
// (undocumented)
getProcessorName(): string;
// (undocumented)
diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json
index 8b4edcbc20..0082898ffc 100644
--- a/plugins/catalog-backend/package.json
+++ b/plugins/catalog-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend",
"description": "The Backstage backend plugin that provides the Backstage catalog",
- "version": "1.12.0-next.0",
+ "version": "1.12.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/catalog-backend/src/index.ts b/plugins/catalog-backend/src/index.ts
index 345102adcd..55c1c672d3 100644
--- a/plugins/catalog-backend/src/index.ts
+++ b/plugins/catalog-backend/src/index.ts
@@ -36,13 +36,13 @@ import {
/**
* @public
- * @deprecated import from `@backstage/search-backend-module-catalog` instead
+ * @deprecated import from `@backstage/plugin-search-backend-module-catalog` instead
*/
export const DefaultCatalogCollatorFactory = _DefaultCatalogCollatorFactory;
/**
* @public
- * @deprecated import from `@backstage/search-backend-module-catalog` instead
+ * @deprecated import from `@backstage/plugin-search-backend-module-catalog` instead
*/
export const defaultCatalogCollatorEntityTransformer =
_defaultCatalogCollatorEntityTransformer;
@@ -54,14 +54,14 @@ import type {
/**
* @public
- * @deprecated import from `@backstage/search-backend-module-catalog` instead
+ * @deprecated import from `@backstage/plugin-search-backend-module-catalog` instead
*/
export type DefaultCatalogCollatorFactoryOptions =
_DefaultCatalogCollatorFactoryOptions;
/**
* @public
- * @deprecated import from `@backstage/search-backend-module-catalog` instead
+ * @deprecated import from `@backstage/plugin-search-backend-module-catalog` instead
*/
export type CatalogCollatorEntityTransformer =
_CatalogCollatorEntityTransformer;
diff --git a/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.test.ts b/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.test.ts
index bafac6e3ac..0cde9417db 100644
--- a/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.test.ts
+++ b/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.test.ts
@@ -109,6 +109,73 @@ describe('AnnotateScmSlugEntityProcessor', () => {
},
});
});
+
+ it('should only process applicable kinds', async () => {
+ const component: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ name: 'my-component',
+ },
+ };
+
+ const api: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'API',
+ metadata: {
+ name: 'my-component',
+ },
+ };
+
+ const system: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'System',
+ metadata: {
+ name: 'my-component',
+ },
+ };
+
+ const location: LocationSpec = {
+ type: 'url',
+ target:
+ 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml',
+ };
+
+ const processor = AnnotateScmSlugEntityProcessor.fromConfig(
+ new ConfigReader({}),
+ { kinds: ['API', 'System'] },
+ );
+
+ expect(await processor.preProcessEntity(component, location)).toEqual({
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ name: 'my-component',
+ },
+ });
+
+ expect(await processor.preProcessEntity(api, location)).toEqual({
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'API',
+ metadata: {
+ name: 'my-component',
+ annotations: {
+ 'github.com/project-slug': 'backstage/backstage',
+ },
+ },
+ });
+
+ expect(await processor.preProcessEntity(system, location)).toEqual({
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'System',
+ metadata: {
+ name: 'my-component',
+ annotations: {
+ 'github.com/project-slug': 'backstage/backstage',
+ },
+ },
+ });
+ });
});
describe('gitlab', () => {
it('adds annotation', async () => {
diff --git a/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.ts
index 2c18ba1d7d..241a27433e 100644
--- a/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.ts
+++ b/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.ts
@@ -31,16 +31,23 @@ const GITLAB_ACTIONS_ANNOTATION = 'gitlab.com/project-slug';
/** @public */
export class AnnotateScmSlugEntityProcessor implements CatalogProcessor {
constructor(
- private readonly opts: { scmIntegrationRegistry: ScmIntegrationRegistry },
+ private readonly opts: {
+ scmIntegrationRegistry: ScmIntegrationRegistry;
+ kinds?: string[];
+ },
) {}
getProcessorName(): string {
return 'AnnotateScmSlugEntityProcessor';
}
- static fromConfig(config: Config): AnnotateScmSlugEntityProcessor {
+ static fromConfig(
+ config: Config,
+ options?: { kinds?: string[] },
+ ): AnnotateScmSlugEntityProcessor {
return new AnnotateScmSlugEntityProcessor({
scmIntegrationRegistry: ScmIntegrations.fromConfig(config),
+ kinds: options?.kinds,
});
}
@@ -48,7 +55,13 @@ export class AnnotateScmSlugEntityProcessor implements CatalogProcessor {
entity: Entity,
location: LocationSpec,
): Promise {
- if (entity.kind !== 'Component' || location.type !== 'url') {
+ const applicableKinds = (this.opts.kinds ?? ['Component']).map(k =>
+ k.toLocaleLowerCase('en-US'),
+ );
+ if (
+ !applicableKinds.includes(entity.kind.toLocaleLowerCase('en-US')) ||
+ location.type !== 'url'
+ ) {
return entity;
}
diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts
index 8e2f8b2f6f..bed1fe6f50 100644
--- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts
+++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts
@@ -23,7 +23,7 @@ import { assertError, serializeError, stringifyError } from '@backstage/errors';
import { Hash } from 'crypto';
import stableStringify from 'fast-json-stable-stringify';
import { Logger } from 'winston';
-import { metrics } from '@opentelemetry/api';
+import { metrics, trace } from '@opentelemetry/api';
import { ProcessingDatabase, RefreshStateItem } from '../database/types';
import { createCounterMetric, createSummaryMetric } from '../util/metrics';
import {
@@ -35,9 +35,16 @@ import { Stitcher } from '../stitching/Stitcher';
import { startTaskPipeline } from './TaskPipeline';
import { PluginTaskScheduler } from '@backstage/backend-tasks';
import { Config } from '@backstage/config';
+import {
+ addEntityAttributes,
+ TRACER_ID,
+ withActiveSpan,
+} from '../util/opentelemetry';
const CACHE_TTL = 5;
+const tracer = trace.getTracer(TRACER_ID);
+
export type ProgressTracker = ReturnType;
export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine {
@@ -131,177 +138,181 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine {
}
},
processTask: async item => {
- const track = this.tracker.processStart(item, this.logger);
+ await withActiveSpan(tracer, 'ProcessingRun', async span => {
+ const track = this.tracker.processStart(item, this.logger);
+ addEntityAttributes(span, item.unprocessedEntity);
- try {
- const {
- id,
- state,
- unprocessedEntity,
- entityRef,
- locationKey,
- resultHash: previousResultHash,
- } = item;
- const result = await this.orchestrator.process({
- entity: unprocessedEntity,
- state,
- });
+ try {
+ const {
+ id,
+ state,
+ unprocessedEntity,
+ entityRef,
+ locationKey,
+ resultHash: previousResultHash,
+ } = item;
+ const result = await this.orchestrator.process({
+ entity: unprocessedEntity,
+ state,
+ });
- track.markProcessorsCompleted(result);
+ track.markProcessorsCompleted(result);
- if (result.ok) {
- const { ttl: _, ...stateWithoutTtl } = state ?? {};
- if (
- stableStringify(stateWithoutTtl) !== stableStringify(result.state)
- ) {
+ if (result.ok) {
+ const { ttl: _, ...stateWithoutTtl } = state ?? {};
+ if (
+ stableStringify(stateWithoutTtl) !==
+ stableStringify(result.state)
+ ) {
+ await this.processingDatabase.transaction(async tx => {
+ await this.processingDatabase.updateEntityCache(tx, {
+ id,
+ state: {
+ ttl: CACHE_TTL,
+ ...result.state,
+ },
+ });
+ });
+ }
+ } else {
+ const maybeTtl = state?.ttl;
+ const ttl = Number.isInteger(maybeTtl) ? (maybeTtl as number) : 0;
await this.processingDatabase.transaction(async tx => {
await this.processingDatabase.updateEntityCache(tx, {
id,
- state: {
- ttl: CACHE_TTL,
- ...result.state,
- },
+ state: ttl > 0 ? { ...state, ttl: ttl - 1 } : {},
});
});
}
- } else {
- const maybeTtl = state?.ttl;
- const ttl = Number.isInteger(maybeTtl) ? (maybeTtl as number) : 0;
- await this.processingDatabase.transaction(async tx => {
- await this.processingDatabase.updateEntityCache(tx, {
- id,
- state: ttl > 0 ? { ...state, ttl: ttl - 1 } : {},
+
+ const location =
+ unprocessedEntity?.metadata?.annotations?.[ANNOTATION_LOCATION];
+ for (const error of result.errors) {
+ this.logger.warn(error.message, {
+ entity: entityRef,
+ location,
});
- });
- }
+ }
+ const errorsString = JSON.stringify(
+ result.errors.map(e => serializeError(e)),
+ );
- const location =
- unprocessedEntity?.metadata?.annotations?.[ANNOTATION_LOCATION];
- for (const error of result.errors) {
- this.logger.warn(error.message, {
- entity: entityRef,
- location,
- });
- }
- const errorsString = JSON.stringify(
- result.errors.map(e => serializeError(e)),
- );
+ let hashBuilder = this.createHash().update(errorsString);
- let hashBuilder = this.createHash().update(errorsString);
-
- if (result.ok) {
- const { entityRefs: parents } =
- await this.processingDatabase.transaction(tx =>
- this.processingDatabase.listParents(tx, {
- entityRef,
- }),
- );
-
- hashBuilder = hashBuilder
- .update(stableStringify({ ...result.completedEntity }))
- .update(stableStringify([...result.deferredEntities]))
- .update(stableStringify([...result.relations]))
- .update(stableStringify([...result.refreshKeys]))
- .update(stableStringify([...parents]));
- }
-
- const resultHash = hashBuilder.digest('hex');
- if (resultHash === previousResultHash) {
- // If nothing changed in our produced outputs, we cannot have any
- // significant effect on our surroundings; therefore, we just abort
- // without any updates / stitching.
- track.markSuccessfulWithNoChanges();
- return;
- }
-
- // If the result was marked as not OK, it signals that some part of the
- // processing pipeline threw an exception. This can happen both as part of
- // non-catastrophic things such as due to validation errors, as well as if
- // something fatal happens inside the processing for other reasons. In any
- // case, this means we can't trust that anything in the output is okay. So
- // just store the errors and trigger a stich so that they become visible to
- // the outside.
- if (!result.ok) {
- // notify the error listener if the entity can not be processed.
- Promise.resolve(undefined)
- .then(() =>
- this.onProcessingError?.({
- unprocessedEntity,
- errors: result.errors,
- }),
- )
- .catch(error => {
- this.logger.debug(
- `Processing error listener threw an exception, ${stringifyError(
- error,
- )}`,
+ if (result.ok) {
+ const { entityRefs: parents } =
+ await this.processingDatabase.transaction(tx =>
+ this.processingDatabase.listParents(tx, {
+ entityRef,
+ }),
);
- });
+ hashBuilder = hashBuilder
+ .update(stableStringify({ ...result.completedEntity }))
+ .update(stableStringify([...result.deferredEntities]))
+ .update(stableStringify([...result.relations]))
+ .update(stableStringify([...result.refreshKeys]))
+ .update(stableStringify([...parents]));
+ }
+
+ const resultHash = hashBuilder.digest('hex');
+ if (resultHash === previousResultHash) {
+ // If nothing changed in our produced outputs, we cannot have any
+ // significant effect on our surroundings; therefore, we just abort
+ // without any updates / stitching.
+ track.markSuccessfulWithNoChanges();
+ return;
+ }
+
+ // If the result was marked as not OK, it signals that some part of the
+ // processing pipeline threw an exception. This can happen both as part of
+ // non-catastrophic things such as due to validation errors, as well as if
+ // something fatal happens inside the processing for other reasons. In any
+ // case, this means we can't trust that anything in the output is okay. So
+ // just store the errors and trigger a stich so that they become visible to
+ // the outside.
+ if (!result.ok) {
+ // notify the error listener if the entity can not be processed.
+ Promise.resolve(undefined)
+ .then(() =>
+ this.onProcessingError?.({
+ unprocessedEntity,
+ errors: result.errors,
+ }),
+ )
+ .catch(error => {
+ this.logger.debug(
+ `Processing error listener threw an exception, ${stringifyError(
+ error,
+ )}`,
+ );
+ });
+
+ await this.processingDatabase.transaction(async tx => {
+ await this.processingDatabase.updateProcessedEntityErrors(tx, {
+ id,
+ errors: errorsString,
+ resultHash,
+ });
+ });
+ await this.stitcher.stitch(
+ new Set([stringifyEntityRef(unprocessedEntity)]),
+ );
+ track.markSuccessfulWithErrors();
+ return;
+ }
+
+ result.completedEntity.metadata.uid = id;
+ let oldRelationSources: Map;
await this.processingDatabase.transaction(async tx => {
- await this.processingDatabase.updateProcessedEntityErrors(tx, {
- id,
- errors: errorsString,
- resultHash,
- });
+ const { previous } =
+ await this.processingDatabase.updateProcessedEntity(tx, {
+ id,
+ processedEntity: result.completedEntity,
+ resultHash,
+ errors: errorsString,
+ relations: result.relations,
+ deferredEntities: result.deferredEntities,
+ locationKey,
+ refreshKeys: result.refreshKeys,
+ });
+ oldRelationSources = new Map(
+ previous.relations.map(r => [
+ `${r.source_entity_ref}:${r.type}`,
+ r.source_entity_ref,
+ ]),
+ );
});
- await this.stitcher.stitch(
- new Set([stringifyEntityRef(unprocessedEntity)]),
+
+ const newRelationSources = new Map(
+ result.relations.map(relation => {
+ const sourceEntityRef = stringifyEntityRef(relation.source);
+ return [`${sourceEntityRef}:${relation.type}`, sourceEntityRef];
+ }),
);
- track.markSuccessfulWithErrors();
- return;
+
+ const setOfThingsToStitch = new Set([
+ stringifyEntityRef(result.completedEntity),
+ ]);
+ newRelationSources.forEach((sourceEntityRef, uniqueKey) => {
+ if (!oldRelationSources.has(uniqueKey)) {
+ setOfThingsToStitch.add(sourceEntityRef);
+ }
+ });
+ oldRelationSources!.forEach((sourceEntityRef, uniqueKey) => {
+ if (!newRelationSources.has(uniqueKey)) {
+ setOfThingsToStitch.add(sourceEntityRef);
+ }
+ });
+
+ await this.stitcher.stitch(setOfThingsToStitch);
+
+ track.markSuccessfulWithChanges(setOfThingsToStitch.size);
+ } catch (error) {
+ assertError(error);
+ track.markFailed(error);
}
-
- result.completedEntity.metadata.uid = id;
- let oldRelationSources: Map;
- await this.processingDatabase.transaction(async tx => {
- const { previous } =
- await this.processingDatabase.updateProcessedEntity(tx, {
- id,
- processedEntity: result.completedEntity,
- resultHash,
- errors: errorsString,
- relations: result.relations,
- deferredEntities: result.deferredEntities,
- locationKey,
- refreshKeys: result.refreshKeys,
- });
- oldRelationSources = new Map(
- previous.relations.map(r => [
- `${r.source_entity_ref}:${r.type}`,
- r.source_entity_ref,
- ]),
- );
- });
-
- const newRelationSources = new Map(
- result.relations.map(relation => {
- const sourceEntityRef = stringifyEntityRef(relation.source);
- return [`${sourceEntityRef}:${relation.type}`, sourceEntityRef];
- }),
- );
-
- const setOfThingsToStitch = new Set([
- stringifyEntityRef(result.completedEntity),
- ]);
- newRelationSources.forEach((sourceEntityRef, uniqueKey) => {
- if (!oldRelationSources.has(uniqueKey)) {
- setOfThingsToStitch.add(sourceEntityRef);
- }
- });
- oldRelationSources!.forEach((sourceEntityRef, uniqueKey) => {
- if (!newRelationSources.has(uniqueKey)) {
- setOfThingsToStitch.add(sourceEntityRef);
- }
- });
-
- await this.stitcher.stitch(setOfThingsToStitch);
-
- track.markSuccessfulWithChanges(setOfThingsToStitch.size);
- } catch (error) {
- assertError(error);
- track.markFailed(error);
- }
+ });
},
});
}
diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts
index ceb6992d7a..fc6855f6df 100644
--- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts
+++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts
@@ -194,10 +194,12 @@ describe('DefaultCatalogProcessingOrchestrator', () => {
it('runs all processor validations when asked to', async () => {
const validate = jest.fn(async () => true);
- const processor1: Partial = {
+ const processor1: CatalogProcessor = {
+ getProcessorName: () => 'processor1',
validateEntityKind: validate,
};
- const processor2: Partial = {
+ const processor2: CatalogProcessor = {
+ getProcessorName: () => 'processor2',
validateEntityKind: validate,
};
diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts
index d177a7d4a8..c5eaacbc68 100644
--- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts
+++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import { Span, trace } from '@opentelemetry/api';
import {
Entity,
EntityPolicy,
@@ -55,6 +56,13 @@ import {
} from './util';
import { CatalogRulesEnforcer } from '../ingestion/CatalogRules';
import { ProcessorCacheManager } from './ProcessorCacheManager';
+import {
+ addEntityAttributes,
+ TRACER_ID,
+ withActiveSpan,
+} from '../util/opentelemetry';
+
+const tracer = trace.getTracer(TRACER_ID);
type Context = {
entityRef: string;
@@ -64,6 +72,18 @@ type Context = {
cache: ProcessorCacheManager;
};
+function addProcessorAttributes(
+ span: Span,
+ stage: string,
+ processor: CatalogProcessor,
+) {
+ span.setAttribute('backstage.catalog.processor.stage', stage);
+ span.setAttribute(
+ 'backstage.catalog.processor.name',
+ processor.getProcessorName(),
+ );
+}
+
/** @public */
export class DefaultCatalogProcessingOrchestrator
implements CatalogProcessingOrchestrator
@@ -179,54 +199,71 @@ export class DefaultCatalogProcessingOrchestrator
entity: Entity,
context: Context,
): Promise {
- let res = entity;
+ return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {
+ addEntityAttributes(stageSpan, entity);
+ stageSpan.setAttribute('backstage.catalog.processor.stage', 'preProcess');
+ let res = entity;
- for (const processor of this.options.processors) {
- if (processor.preProcessEntity) {
- try {
- res = await processor.preProcessEntity(
- res,
- context.location,
- context.collector.forProcessor(processor),
- context.originLocation,
- context.cache.forProcessor(processor),
- );
- } catch (e) {
- throw new InputError(
- `Processor ${processor.constructor.name} threw an error while preprocessing`,
- e,
- );
+ for (const processor of this.options.processors) {
+ if (processor.preProcessEntity) {
+ let innerRes = res;
+ res = await withActiveSpan(tracer, 'ProcessingStep', async span => {
+ addEntityAttributes(span, entity);
+ addProcessorAttributes(span, 'preProcessEntity', processor);
+ try {
+ innerRes = await processor.preProcessEntity!(
+ innerRes,
+ context.location,
+ context.collector.forProcessor(processor),
+ context.originLocation,
+ context.cache.forProcessor(processor),
+ );
+ } catch (e) {
+ throw new InputError(
+ `Processor ${processor.constructor.name} threw an error while preprocessing`,
+ e,
+ );
+ }
+ return innerRes;
+ });
}
}
- }
- return res;
+ return res;
+ });
}
/**
* Enforce entity policies making sure that entities conform to a general schema
*/
private async runPolicyStep(entity: Entity): Promise {
- let policyEnforcedEntity: Entity | undefined;
-
- try {
- policyEnforcedEntity = await this.options.policy.enforce(entity);
- } catch (e) {
- throw new InputError(
- `Policy check failed for ${stringifyEntityRef(entity)}`,
- e,
+ return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {
+ addEntityAttributes(stageSpan, entity);
+ stageSpan.setAttribute(
+ 'backstage.catalog.processor.stage',
+ 'enforcePolicy',
);
- }
+ let policyEnforcedEntity: Entity | undefined;
- if (!policyEnforcedEntity) {
- throw new Error(
- `Policy unexpectedly returned no data for ${stringifyEntityRef(
- entity,
- )}`,
- );
- }
+ try {
+ policyEnforcedEntity = await this.options.policy.enforce(entity);
+ } catch (e) {
+ throw new InputError(
+ `Policy check failed for ${stringifyEntityRef(entity)}`,
+ e,
+ );
+ }
- return policyEnforcedEntity;
+ if (!policyEnforcedEntity) {
+ throw new Error(
+ `Policy unexpectedly returned no data for ${stringifyEntityRef(
+ entity,
+ )}`,
+ );
+ }
+
+ return policyEnforcedEntity;
+ });
}
/**
@@ -236,50 +273,62 @@ export class DefaultCatalogProcessingOrchestrator
entity: Entity,
context: Context,
): Promise {
- // Double check that none of the previous steps tried to change something
- // related to the entity ref, which would break downstream
- if (stringifyEntityRef(entity) !== context.entityRef) {
- throw new ConflictError(
- 'Fatal: The entity kind, namespace, or name changed during processing',
- );
- }
+ return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {
+ addEntityAttributes(stageSpan, entity);
+ stageSpan.setAttribute('backstage.catalog.processor.stage', 'validate');
+ // Double check that none of the previous steps tried to change something
+ // related to the entity ref, which would break downstream
+ if (stringifyEntityRef(entity) !== context.entityRef) {
+ throw new ConflictError(
+ 'Fatal: The entity kind, namespace, or name changed during processing',
+ );
+ }
- // Validate that the end result is a valid Entity at all
- try {
- validateEntity(entity);
- } catch (e) {
- throw new ConflictError(
- `Entity envelope for ${context.entityRef} failed validation after preprocessing`,
- e,
- );
- }
+ // Validate that the end result is a valid Entity at all
+ try {
+ validateEntity(entity);
+ } catch (e) {
+ throw new ConflictError(
+ `Entity envelope for ${context.entityRef} failed validation after preprocessing`,
+ e,
+ );
+ }
- let valid = false;
+ let valid = false;
- for (const processor of this.options.processors) {
- if (processor.validateEntityKind) {
- try {
- const thisValid = await processor.validateEntityKind(entity);
- if (thisValid) {
- valid = true;
- if (this.options.legacySingleProcessorValidation) {
- break;
+ for (const processor of this.options.processors) {
+ if (processor.validateEntityKind) {
+ try {
+ const thisValid = await withActiveSpan(
+ tracer,
+ 'ProcessingStep',
+ async span => {
+ addEntityAttributes(span, entity);
+ addProcessorAttributes(span, 'validateEntityKind', processor);
+ return await processor.validateEntityKind!(entity);
+ },
+ );
+ if (thisValid) {
+ valid = true;
+ if (this.options.legacySingleProcessorValidation) {
+ break;
+ }
}
+ } catch (e) {
+ throw new InputError(
+ `Processor ${processor.constructor.name} threw an error while validating the entity ${context.entityRef}`,
+ e,
+ );
}
- } catch (e) {
- throw new InputError(
- `Processor ${processor.constructor.name} threw an error while validating the entity ${context.entityRef}`,
- e,
- );
}
}
- }
- if (!valid) {
- throw new InputError(
- `No processor recognized the entity ${context.entityRef} as valid, possibly caused by a foreign kind or apiVersion`,
- );
- }
+ if (!valid) {
+ throw new InputError(
+ `No processor recognized the entity ${context.entityRef} as valid, possibly caused by a foreign kind or apiVersion`,
+ );
+ }
+ });
}
/**
@@ -289,65 +338,81 @@ export class DefaultCatalogProcessingOrchestrator
entity: LocationEntity,
context: Context,
): Promise {
- const { type = context.location.type, presence = 'required' } = entity.spec;
- const targets = new Array();
- if (entity.spec.target) {
- targets.push(entity.spec.target);
- }
- if (entity.spec.targets) {
- targets.push(...entity.spec.targets);
- }
-
- for (const maybeRelativeTarget of targets) {
- if (type === 'file' && maybeRelativeTarget.endsWith(path.sep)) {
- context.collector.generic()(
- processingResult.inputError(
- context.location,
- `LocationEntityProcessor cannot handle ${type} type location with target ${context.location.target} that ends with a path separator`,
- ),
- );
- continue;
- }
- const target = toAbsoluteUrl(
- this.options.integrations,
- context.location,
- type,
- maybeRelativeTarget,
+ return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {
+ addEntityAttributes(stageSpan, entity);
+ stageSpan.setAttribute(
+ 'backstage.catalog.processor.stage',
+ 'readLocation',
);
+ const { type = context.location.type, presence = 'required' } =
+ entity.spec;
+ const targets = new Array();
+ if (entity.spec.target) {
+ targets.push(entity.spec.target);
+ }
+ if (entity.spec.targets) {
+ targets.push(...entity.spec.targets);
+ }
- let didRead = false;
- for (const processor of this.options.processors) {
- if (processor.readLocation) {
- try {
- const read = await processor.readLocation(
- {
- type,
- target,
- presence,
- },
- presence === 'optional',
- context.collector.forProcessor(processor),
- this.options.parser,
- context.cache.forProcessor(processor, target),
- );
- if (read) {
- didRead = true;
- break;
+ for (const maybeRelativeTarget of targets) {
+ if (type === 'file' && maybeRelativeTarget.endsWith(path.sep)) {
+ context.collector.generic()(
+ processingResult.inputError(
+ context.location,
+ `LocationEntityProcessor cannot handle ${type} type location with target ${context.location.target} that ends with a path separator`,
+ ),
+ );
+ continue;
+ }
+ const target = toAbsoluteUrl(
+ this.options.integrations,
+ context.location,
+ type,
+ maybeRelativeTarget,
+ );
+
+ let didRead = false;
+ for (const processor of this.options.processors) {
+ if (processor.readLocation) {
+ try {
+ const read = await withActiveSpan(
+ tracer,
+ 'ProcessingStep',
+ async span => {
+ addEntityAttributes(span, entity);
+ addProcessorAttributes(span, 'readLocation', processor);
+ return await processor.readLocation!(
+ {
+ type,
+ target,
+ presence,
+ },
+ presence === 'optional',
+ context.collector.forProcessor(processor),
+ this.options.parser,
+ context.cache.forProcessor(processor, target),
+ );
+ },
+ );
+ if (read) {
+ didRead = true;
+ break;
+ }
+ } catch (e) {
+ throw new InputError(
+ `Processor ${processor.constructor.name} threw an error while reading ${type}:${target}`,
+ e,
+ );
}
- } catch (e) {
- throw new InputError(
- `Processor ${processor.constructor.name} threw an error while reading ${type}:${target}`,
- e,
- );
}
}
+ if (!didRead) {
+ throw new InputError(
+ `No processor was able to handle reading of ${type}:${target}`,
+ );
+ }
}
- if (!didRead) {
- throw new InputError(
- `No processor was able to handle reading of ${type}:${target}`,
- );
- }
- }
+ });
}
/**
@@ -357,26 +422,39 @@ export class DefaultCatalogProcessingOrchestrator
entity: Entity,
context: Context,
): Promise {
- let res = entity;
+ return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {
+ addEntityAttributes(stageSpan, entity);
+ stageSpan.setAttribute(
+ 'backstage.catalog.processor.stage',
+ 'postProcessEntity',
+ );
+ let res = entity;
- for (const processor of this.options.processors) {
- if (processor.postProcessEntity) {
- try {
- res = await processor.postProcessEntity(
- res,
- context.location,
- context.collector.forProcessor(processor),
- context.cache.forProcessor(processor),
- );
- } catch (e) {
- throw new InputError(
- `Processor ${processor.constructor.name} threw an error while postprocessing`,
- e,
- );
+ for (const processor of this.options.processors) {
+ if (processor.postProcessEntity) {
+ let innerRes = res;
+ res = await withActiveSpan(tracer, 'ProcessingStep', async span => {
+ addEntityAttributes(span, entity);
+ addProcessorAttributes(span, 'postProcessEntity', processor);
+ try {
+ innerRes = await processor.postProcessEntity!(
+ innerRes,
+ context.location,
+ context.collector.forProcessor(processor),
+ context.cache.forProcessor(processor),
+ );
+ } catch (e) {
+ throw new InputError(
+ `Processor ${processor.constructor.name} threw an error while postprocessing`,
+ e,
+ );
+ }
+ return innerRes;
+ });
}
}
- }
- return res;
+ return res;
+ });
}
}
diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts
index d77b034517..b8d5bdbeda 100644
--- a/plugins/catalog-backend/src/service/CatalogPlugin.ts
+++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts
@@ -84,7 +84,7 @@ export const catalogPlugin = createBackendPlugin({
env.registerInit({
deps: {
logger: coreServices.logger,
- config: coreServices.config,
+ config: coreServices.rootConfig,
reader: coreServices.urlReader,
permissions: coreServices.permissions,
database: coreServices.database,
diff --git a/plugins/catalog-backend/src/tests/performance/lib/catalogModuleSyntheticLoadEntities.ts b/plugins/catalog-backend/src/tests/performance/lib/catalogModuleSyntheticLoadEntities.ts
index 4d38b28bcf..7925b0b552 100644
--- a/plugins/catalog-backend/src/tests/performance/lib/catalogModuleSyntheticLoadEntities.ts
+++ b/plugins/catalog-backend/src/tests/performance/lib/catalogModuleSyntheticLoadEntities.ts
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-import { createBackendModule } from '@backstage/backend-plugin-api';
import { Entity } from '@backstage/catalog-model';
import { LocationSpec } from '@backstage/plugin-catalog-common';
import {
@@ -26,7 +25,6 @@ import {
EntityProviderConnection,
processingResult,
} from '@backstage/plugin-catalog-node';
-import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
/**
* Options for a fixed initial load of entities.
@@ -143,12 +141,15 @@ export const common = {
/**
* The entity provider that drives the initial base entity injection
+ * @internal
*/
-class SyntheticLoadEntitiesProvider implements EntityProvider {
+export class SyntheticLoadEntitiesProvider implements EntityProvider {
constructor(
private readonly load: SyntheticLoadOptions,
private readonly events: SyntheticLoadEvents,
- ) {}
+ ) {
+ validateSyntheticLoadOptions(load);
+ }
getProviderName(): string {
return 'SyntheticLoadEntitiesProvider';
@@ -180,9 +181,12 @@ class SyntheticLoadEntitiesProvider implements EntityProvider {
/**
* Supporting processor for emitting children and relations
+ * @internal
*/
-class SyntheticLoadEntitiesProcessor implements CatalogProcessor {
- constructor(private readonly load: SyntheticLoadOptions) {}
+export class SyntheticLoadEntitiesProcessor implements CatalogProcessor {
+ constructor(private readonly load: SyntheticLoadOptions) {
+ validateSyntheticLoadOptions(load);
+ }
getProcessorName(): string {
return 'SyntheticLoadEntitiesProcessor';
@@ -231,27 +235,3 @@ class SyntheticLoadEntitiesProcessor implements CatalogProcessor {
return entity;
}
}
-
-export const catalogModuleSyntheticLoadEntities = createBackendModule(
- (options: { load: SyntheticLoadOptions; events?: SyntheticLoadEvents }) => ({
- moduleId: 'syntheticLoadEntities',
- pluginId: 'catalog',
- register(reg) {
- reg.registerInit({
- deps: {
- catalog: catalogProcessingExtensionPoint,
- },
- async init({ catalog }) {
- const { load, events = {} } = options;
-
- validateSyntheticLoadOptions(load);
- const provider = new SyntheticLoadEntitiesProvider(load, events);
- const processor = new SyntheticLoadEntitiesProcessor(load);
-
- catalog.addEntityProvider(provider);
- catalog.addProcessor(processor);
- },
- });
- },
- }),
-);
diff --git a/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts b/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts
index 2f32fcd907..fb2bd06b96 100644
--- a/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts
+++ b/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts
@@ -16,16 +16,19 @@
import {
coreServices,
+ createBackendModule,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { TestDatabases, startTestBackend } from '@backstage/backend-test-utils';
import { catalogPlugin } from '@backstage/plugin-catalog-backend/alpha';
+import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha';
import { Knex } from 'knex';
import { applyDatabaseMigrations } from '../../database/migrations';
import {
SyntheticLoadEvents,
SyntheticLoadOptions,
- catalogModuleSyntheticLoadEntities,
+ SyntheticLoadEntitiesProvider,
+ SyntheticLoadEntitiesProcessor,
} from './lib/catalogModuleSyntheticLoadEntities';
import { describePerformanceTest, performanceTraceEnabled } from './lib/env';
@@ -177,13 +180,28 @@ describePerformanceTest('stitchingPerformance', () => {
const tracker = new Tracker(knex, load);
const backend = await startTestBackend({
- services: [staticDatabase(knex)],
features: [
catalogPlugin(),
- catalogModuleSyntheticLoadEntities({
- load,
- events: tracker.events(),
+ createBackendModule({
+ moduleId: 'syntheticLoadEntities',
+ pluginId: 'catalog',
+ register(reg) {
+ reg.registerInit({
+ deps: {
+ catalog: catalogProcessingExtensionPoint,
+ },
+ async init({ catalog }) {
+ catalog.addEntityProvider(
+ new SyntheticLoadEntitiesProvider(load, tracker.events()),
+ );
+ catalog.addProcessor(
+ new SyntheticLoadEntitiesProcessor(load),
+ );
+ },
+ });
+ },
}),
+ staticDatabase(knex),
],
});
diff --git a/plugins/catalog-backend/src/util/opentelemetry.ts b/plugins/catalog-backend/src/util/opentelemetry.ts
new file mode 100644
index 0000000000..3fb83d7909
--- /dev/null
+++ b/plugins/catalog-backend/src/util/opentelemetry.ts
@@ -0,0 +1,101 @@
+/*
+ * 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 { Span, SpanOptions, SpanStatusCode, Tracer } from '@opentelemetry/api';
+import { Entity } from '@backstage/catalog-model';
+
+export const TRACER_ID = 'backstage-plugin-catalog-backend';
+
+function setAttributeIfDefined(span: Span, attribute: string, value?: string) {
+ if (value !== null && value !== undefined) {
+ span.setAttribute(attribute, value);
+ }
+}
+
+export function addEntityAttributes(span: Span, entity: Entity) {
+ setAttributeIfDefined(span, 'backstage.entity.apiVersion', entity.apiVersion);
+ setAttributeIfDefined(span, 'backstage.entity.kind', entity.kind);
+ setAttributeIfDefined(
+ span,
+ 'backstage.entity.metadata.namespace',
+ entity.metadata?.namespace,
+ );
+ setAttributeIfDefined(
+ span,
+ 'backstage.entity.metadata.name',
+ entity.metadata?.name,
+ );
+}
+
+// Adapted from https://github.com/open-telemetry/opentelemetry-js/blob/359fbcc40a859057a02b14e84599eac399b8dba7/api/src/trace/SugaredTracer.ts
+// While waiting for something like https://github.com/open-telemetry/opentelemetry-js/pull/3317 to land upstream
+
+const onException = (e: Error, span: Span) => {
+ span.recordException(e);
+ span.setStatus({
+ code: SpanStatusCode.ERROR,
+ });
+};
+
+function isPromiseLike(obj: PromiseLike | S): obj is PromiseLike {
+ return (
+ !!obj &&
+ (typeof obj === 'object' || typeof obj === 'function') &&
+ 'then' in obj &&
+ typeof obj.then === 'function'
+ );
+}
+
+function handleFn ReturnType