diff --git a/.changeset/beige-kiwis-know.md b/.changeset/beige-kiwis-know.md new file mode 100644 index 0000000000..696b9e81cb --- /dev/null +++ b/.changeset/beige-kiwis-know.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +Introduced new package for creating backend plugins using the new alpha backend plugin framework. +This package is still considered **EXPERIMENTAL** and things will change without warning. Do not use this for production. diff --git a/.changeset/eleven-mice-collect.md b/.changeset/eleven-mice-collect.md deleted file mode 100644 index 66c4591d8e..0000000000 --- a/.changeset/eleven-mice-collect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': minor ---- - -Added a new `/v2/dry-run` endpoint that allows for a synchronous dry run of a provided template. A `supportsDryRun` option has been added to `createTemplateAction`, which signals whether the action should be executed during dry runs. When enabled, the action context will have the new `isDryRun` property set to signal if the action is being executed during a dry run. diff --git a/.changeset/hungry-cougars-press.md b/.changeset/hungry-cougars-press.md new file mode 100644 index 0000000000..c99e1f4c76 --- /dev/null +++ b/.changeset/hungry-cougars-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +--- + +Fix mapping between users and groups for FreeIPA when using the LdapOrgProcessor diff --git a/.changeset/lazy-steaks-tell.md b/.changeset/lazy-steaks-tell.md new file mode 100644 index 0000000000..5e63816ada --- /dev/null +++ b/.changeset/lazy-steaks-tell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-node': minor +--- + +Added alpha exports for the new experimental backend system. diff --git a/.changeset/loud-lemons-listen.md b/.changeset/loud-lemons-listen.md new file mode 100644 index 0000000000..f232c1491e --- /dev/null +++ b/.changeset/loud-lemons-listen.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-node': major +--- + +This package houses stable types from the `@backstage/plugin-catalog-backend` package and is intended for creation of catalog modules. Prefer importing from this package over the `@backstage/plugin-catalog-backend` package. diff --git a/.changeset/perfect-donuts-applaud.md b/.changeset/perfect-donuts-applaud.md new file mode 100644 index 0000000000..3b663fa2c2 --- /dev/null +++ b/.changeset/perfect-donuts-applaud.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Don't resolve symlinks, treat them as binary files and copy them as-is diff --git a/.changeset/polite-eagles-invite.md b/.changeset/polite-eagles-invite.md deleted file mode 100644 index f9c5073a83..0000000000 --- a/.changeset/polite-eagles-invite.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-scaffolder': minor ---- - -A new template editor has been added which is accessible via the context menu on the top right hand corner of the Create page. It allows you to load a template from a local directory, edit it with a preview, execute it in dry-run mode, and view the results. Note that the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API) must be supported by your browser for this to be available. - -To support the new template editor the `ScaffolderApi` now has an optional `dryRun` method, which is implemented by the default `ScaffolderClient`. diff --git a/.changeset/polite-moose-beam.md b/.changeset/polite-moose-beam.md new file mode 100644 index 0000000000..5e41018ae6 --- /dev/null +++ b/.changeset/polite-moose-beam.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +Export experimental `catalogPlugin` for the new backend system. This export is not considered stable and should not be used in production. diff --git a/.changeset/pre.json b/.changeset/pre.json index 7b30cba7ba..1b03a40575 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -176,7 +176,6 @@ "curvy-weeks-matter", "eight-suits-fail", "eighty-windows-brush", - "eleven-mice-collect", "five-fireants-run", "forty-seals-complain", "great-roses-pump", @@ -196,7 +195,6 @@ "nervous-humans-sip", "old-onions-hear", "plenty-clouds-guess", - "polite-eagles-invite", "polite-lions-sell", "popular-pots-yell", "pretty-masks-live", diff --git a/.changeset/renovate-33c3cf4.md b/.changeset/renovate-33c3cf4.md new file mode 100644 index 0000000000..8389a89ea1 --- /dev/null +++ b/.changeset/renovate-33c3cf4.md @@ -0,0 +1,14 @@ +--- +'@backstage/catalog-model': patch +'@backstage/cli': patch +'@backstage/config-loader': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-proxy-backend': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder': patch +--- + +Updated dependency `yaml` to `^2.0.0`. diff --git a/.changeset/renovate-d26eca1.md b/.changeset/renovate-d26eca1.md new file mode 100644 index 0000000000..6ddad0d5fe --- /dev/null +++ b/.changeset/renovate-d26eca1.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-adr': patch +'@backstage/plugin-scaffolder-backend': patch +--- + +Updated dependency `octokit` to `^2.0.0`. diff --git a/.changeset/shaggy-spiders-notice.md b/.changeset/shaggy-spiders-notice.md new file mode 100644 index 0000000000..513f6cd521 --- /dev/null +++ b/.changeset/shaggy-spiders-notice.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +CatalogBuilder supports now subscription to processing engine errors. + +```ts +subscribe(options: { + onProcessingError: (event: { unprocessedEntity: Entity, error: Error }) => Promise | void; +}); +``` + +If you want to get notified on errors while processing the entities, you call CatalogBuilder.subscribe +to get notifications with the parameters defined as above. diff --git a/.changeset/short-wolves-applaud.md b/.changeset/short-wolves-applaud.md new file mode 100644 index 0000000000..fa5cebffc3 --- /dev/null +++ b/.changeset/short-wolves-applaud.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Export experimental `scaffolderCatalogExtension` for the new backend system. This export is not considered stable and should not be used in production. diff --git a/.changeset/swift-plants-fix.md b/.changeset/swift-plants-fix.md new file mode 100644 index 0000000000..52873b7985 --- /dev/null +++ b/.changeset/swift-plants-fix.md @@ -0,0 +1,7 @@ +--- +'@backstage/backend-app-api': minor +--- + +Add initial plumbing for creating backends using the experimental backend framework. + +This package is highly **EXPERIMENTAL** and should not be used in production. diff --git a/.changeset/twelve-peaches-tickle.md b/.changeset/twelve-peaches-tickle.md new file mode 100644 index 0000000000..2a7814ea06 --- /dev/null +++ b/.changeset/twelve-peaches-tickle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Many symbol declarations have been moved to `@backstage/plugin-catalog-node`. This has no affect on users of this package as they are all re-exported. Modules that build on top of the catalog backend plugin should switch all of their imports to the `@backstage/plugin-catalog-node` package and remove the dependency on `@backstage/plugin-catalog-backend`. diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 1444559a68..4fa8a60577 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -26,4 +26,4 @@ jobs: installation-id: ${{ secrets.BACKSTAGE_GOALIE_INSTALLATION_ID }} project-id: PVT_kwDOBFKqdc02LQ excluded-users: ${{ secrets.OOO_USERS }} - owning-teams: '@backstage/techdocs' + owning-teams: '@backstage/techdocs-core' diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md index d6353d1185..30bd4c77bc 100644 --- a/docs/features/software-catalog/external-integrations.md +++ b/docs/features/software-catalog/external-integrations.md @@ -57,7 +57,7 @@ The recommended way of instantiating the catalog backend classes is to use the `CatalogBuilder`, as illustrated in the [example backend here](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/catalog.ts). We will create a new -[`EntityProvider`](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend/src/api/provider.ts) +[`EntityProvider`](https://github.com/backstage/backstage/blob/master/plugins/catalog-node/src/api/provider.ts) subclass that can be added to this catalog builder. Let's make a simple provider that can refresh a set of entities based on a @@ -79,7 +79,7 @@ import { Entity } from '@backstage/catalog-model'; import { EntityProvider, EntityProviderConnection, -} from '@backstage/plugin-catalog-backend'; +} from '@backstage/plugin-catalog-node'; /** * Provides entities from fictional frobs service. @@ -354,7 +354,7 @@ The recommended way of instantiating the catalog backend classes is to use the `CatalogBuilder`, as illustrated in the [example backend here](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/catalog.ts). We will create a new -[`CatalogProcessor`](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend/src/api/processor.ts) +[`CatalogProcessor`](https://github.com/backstage/backstage/blob/master/plugins/catalog-node/src/api/processor.ts) subclass that can be added to this catalog builder. It is up to you where you put the code for this new processor class. For quick @@ -371,7 +371,7 @@ import { CatalogProcessor, CatalogProcessorEmit, LocationSpec, -} from '@backstage/plugin-catalog-backend'; +} from '@backstage/plugin-catalog-node'; // A processor that reads from the fictional System-X export class SystemXReaderProcessor implements CatalogProcessor { @@ -455,7 +455,7 @@ import { CatalogProcessorCache, CatalogProcessorParser, LocationSpec, -} from '@backstage/plugin-catalog-backend'; +} from '@backstage/plugin-catalog-node'; // It's recommended to always bump the CACHE_KEY version if you make // changes to the processor implementation or CacheItem. diff --git a/docs/integrations/ldap/org.md b/docs/integrations/ldap/org.md index e7028f34f1..dbd3f4ff43 100644 --- a/docs/integrations/ldap/org.md +++ b/docs/integrations/ldap/org.md @@ -12,6 +12,10 @@ groups - directly from an LDAP compatible service. The result is a hierarchy of [`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind entities that mirror your org setup. +## Supported vendors + +Backstage in general supports OpenLDAP compatible vendors, as well as Active Directory and FreeIPA. If you are using a vendor that does not seem to be supported, please [file an issue](https://github.com/backstage/backstage/issues/new?assignees=&labels=enhancement&template=feature_template.md). + ## Installation This guide will use the Entity Provider method. If you for some reason prefer diff --git a/packages/backend-app-api/.eslintrc.js b/packages/backend-app-api/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/backend-app-api/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/backend-app-api/README.md b/packages/backend-app-api/README.md new file mode 100644 index 0000000000..ee608ceb43 --- /dev/null +++ b/packages/backend-app-api/README.md @@ -0,0 +1,19 @@ +# @backstage/backend-app-api + +**This package is HIGHLY EXPERIMENTAL, do not use this for production** + +This package provides the core API used by Backstage backend apps. + +## Installation + +Add the library to your backend app package: + +```bash +# From your Backstage root directory +yarn add --cwd packages/backend @backstage/backend-app-api +``` + +## Documentation + +- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md new file mode 100644 index 0000000000..a37ec6abc6 --- /dev/null +++ b/packages/backend-app-api/api-report.md @@ -0,0 +1,25 @@ +## API Report File for "@backstage/backend-app-api" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { AnyServiceFactory } from '@backstage/backend-plugin-api'; +import { BackendRegistrable } from '@backstage/backend-plugin-api'; + +// @public (undocumented) +export interface Backend { + // (undocumented) + add(extension: BackendRegistrable): void; + // (undocumented) + start(): Promise; +} + +// @public (undocumented) +export function createBackend(options?: CreateBackendOptions): Backend; + +// @public (undocumented) +export interface CreateBackendOptions { + // (undocumented) + apis: AnyServiceFactory[]; +} +``` diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json new file mode 100644 index 0000000000..ae6f7eb03b --- /dev/null +++ b/packages/backend-app-api/package.json @@ -0,0 +1,52 @@ +{ + "name": "@backstage/backend-app-api", + "description": "Core API used by Backstage backend apps", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts", + "alphaTypes": "dist/index.alpha.d.ts" + }, + "backstage": { + "role": "node-library" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/backend-app-api" + }, + "keywords": [ + "backstage" + ], + "license": "Apache-2.0", + "scripts": { + "build": "backstage-cli package build --experimental-type-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", + "start": "backstage-cli package start" + }, + "dependencies": { + "@backstage/backend-plugin-api": "^0.0.0", + "@backstage/backend-common": "^0.14.1-next.2", + "@backstage/backend-tasks": "^0.3.3-next.2", + "@backstage/plugin-permission-node": "^0.6.3-next.1", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", + "winston": "^3.2.1" + }, + "devDependencies": { + "@backstage/cli": "^0.18.0-next.2" + }, + "files": [ + "dist", + "alpha" + ] +} diff --git a/packages/backend-app-api/src/index.ts b/packages/backend-app-api/src/index.ts new file mode 100644 index 0000000000..c58379c3e4 --- /dev/null +++ b/packages/backend-app-api/src/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Core API used by Backstage backend apps. + * + * @packageDocumentation + */ + +export * from './wiring'; diff --git a/packages/backend-app-api/src/services/implementations/cacheService.ts b/packages/backend-app-api/src/services/implementations/cacheService.ts new file mode 100644 index 0000000000..034f85f917 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/cacheService.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { CacheManager } from '@backstage/backend-common'; +import { + configServiceRef, + createServiceFactory, + cacheServiceRef, +} from '@backstage/backend-plugin-api'; + +// TODO: Work out some naming and implementation patterns for these +export const cacheFactory = createServiceFactory({ + service: cacheServiceRef, + deps: { + configFactory: configServiceRef, + }, + factory: async ({ configFactory }) => { + const config = await configFactory('root'); + const cacheManager = CacheManager.fromConfig(config); + return async (pluginId: string) => { + return cacheManager.forPlugin(pluginId); + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/configService.ts b/packages/backend-app-api/src/services/implementations/configService.ts new file mode 100644 index 0000000000..94a59baf34 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/configService.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { loadBackendConfig } from '@backstage/backend-common'; +import { + configServiceRef, + createServiceFactory, + loggerToWinstonLogger, + loggerServiceRef, +} from '@backstage/backend-plugin-api'; + +export const configFactory = createServiceFactory({ + service: configServiceRef, + deps: { + loggerFactory: loggerServiceRef, + }, + factory: async ({ loggerFactory }) => { + const logger = await loggerFactory('root'); + const config = await loadBackendConfig({ + argv: process.argv, + logger: loggerToWinstonLogger(logger), + }); + return async () => { + return config; + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/databaseService.ts b/packages/backend-app-api/src/services/implementations/databaseService.ts new file mode 100644 index 0000000000..a52da1e444 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/databaseService.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DatabaseManager } from '@backstage/backend-common'; +import { + configServiceRef, + createServiceFactory, + databaseServiceRef, +} from '@backstage/backend-plugin-api'; + +export const databaseFactory = createServiceFactory({ + service: databaseServiceRef, + deps: { + configFactory: configServiceRef, + }, + factory: async ({ configFactory }) => { + const config = await configFactory('root'); + const databaseManager = DatabaseManager.fromConfig(config); + return async (pluginId: string) => { + return databaseManager.forPlugin(pluginId); + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/discoveryService.ts b/packages/backend-app-api/src/services/implementations/discoveryService.ts new file mode 100644 index 0000000000..23af1924a0 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/discoveryService.ts @@ -0,0 +1,36 @@ +/* + * 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 { SingleHostDiscovery } from '@backstage/backend-common'; +import { + configServiceRef, + createServiceFactory, + discoveryServiceRef, +} from '@backstage/backend-plugin-api'; + +export const discoveryFactory = createServiceFactory({ + service: discoveryServiceRef, + deps: { + configFactory: configServiceRef, + }, + factory: async ({ configFactory }) => { + const config = await configFactory('root'); + const discovery = SingleHostDiscovery.fromConfig(config); + return async () => { + return discovery; + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/httpRouterService.ts b/packages/backend-app-api/src/services/implementations/httpRouterService.ts new file mode 100644 index 0000000000..1f72378643 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/httpRouterService.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createServiceFactory, + httpRouterServiceRef, + configServiceRef, +} from '@backstage/backend-plugin-api'; +import Router from 'express-promise-router'; +import { Handler } from 'express'; +import { createServiceBuilder } from '@backstage/backend-common'; + +export const httpRouterFactory = createServiceFactory({ + service: httpRouterServiceRef, + deps: { + configFactory: configServiceRef, + }, + factory: async ({ configFactory }) => { + const rootRouter = Router(); + + const service = createServiceBuilder(module) + .loadConfig(await configFactory('root')) + .addRouter('', rootRouter); + + await service.start(); + + return async (pluginId?: string) => { + const path = pluginId ? `/api/${pluginId}` : ''; + return { + use(handler: Handler) { + rootRouter.use(path, handler); + }, + }; + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/index.ts b/packages/backend-app-api/src/services/implementations/index.ts new file mode 100644 index 0000000000..e6b6604069 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/index.ts @@ -0,0 +1,39 @@ +/* + * 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 { cacheFactory } from './cacheService'; +import { configFactory } from './configService'; +import { databaseFactory } from './databaseService'; +import { discoveryFactory } from './discoveryService'; +import { loggerFactory } from './loggerService'; +import { permissionsFactory } from './permissionsService'; +import { schedulerFactory } from './schedulerService'; +import { tokenManagerFactory } from './tokenManagerService'; +import { urlReaderFactory } from './urlReaderService'; +import { httpRouterFactory } from './httpRouterService'; + +export const defaultServiceFactories = [ + cacheFactory, + configFactory, + databaseFactory, + discoveryFactory, + loggerFactory, + permissionsFactory, + schedulerFactory, + tokenManagerFactory, + urlReaderFactory, + httpRouterFactory, +]; diff --git a/packages/backend-app-api/src/services/implementations/loggerService.ts b/packages/backend-app-api/src/services/implementations/loggerService.ts new file mode 100644 index 0000000000..fbeb4b9ade --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/loggerService.ts @@ -0,0 +1,50 @@ +/* + * 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 { createRootLogger } from '@backstage/backend-common'; +import { + createServiceFactory, + Logger, + loggerServiceRef, +} from '@backstage/backend-plugin-api'; +import { Logger as WinstonLogger } from 'winston'; + +class BackstageLogger implements Logger { + static fromWinston(logger: WinstonLogger): BackstageLogger { + return new BackstageLogger(logger); + } + + private constructor(private readonly winston: WinstonLogger) {} + + info(message: string, ...meta: any[]): void { + this.winston.info(message, ...meta); + } + + child(fields: { [name: string]: string }): Logger { + return new BackstageLogger(this.winston.child(fields)); + } +} + +export const loggerFactory = createServiceFactory({ + service: loggerServiceRef, + deps: {}, + factory: async () => { + const root = BackstageLogger.fromWinston(createRootLogger()); + return async (pluginId: string) => { + return root.child({ pluginId }); + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/permissionsService.ts b/packages/backend-app-api/src/services/implementations/permissionsService.ts new file mode 100644 index 0000000000..97f0330917 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/permissionsService.ts @@ -0,0 +1,45 @@ +/* + * 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 { + configServiceRef, + createServiceFactory, + discoveryServiceRef, + permissionsServiceRef, + tokenManagerServiceRef, +} from '@backstage/backend-plugin-api'; +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; + +export const permissionsFactory = createServiceFactory({ + service: permissionsServiceRef, + deps: { + configFactory: configServiceRef, + discoveryFactory: discoveryServiceRef, + tokenManagerFactory: tokenManagerServiceRef, + }, + factory: async ({ configFactory, discoveryFactory, tokenManagerFactory }) => { + const config = await configFactory('root'); + const discovery = await discoveryFactory('root'); + const tokenManager = await tokenManagerFactory('root'); + const permissions = ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager, + }); + return async (_pluginId: string) => { + return permissions; + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/schedulerService.ts b/packages/backend-app-api/src/services/implementations/schedulerService.ts new file mode 100644 index 0000000000..130ccb3f5f --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/schedulerService.ts @@ -0,0 +1,36 @@ +/* + * 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 { + configServiceRef, + createServiceFactory, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { TaskScheduler } from '@backstage/backend-tasks'; + +export const schedulerFactory = createServiceFactory({ + service: schedulerServiceRef, + deps: { + configFactory: configServiceRef, + }, + factory: async ({ configFactory }) => { + const config = await configFactory('root'); + const taskScheduler = TaskScheduler.fromConfig(config); + return async (pluginId: string) => { + return taskScheduler.forPlugin(pluginId); + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/tokenManagerService.ts b/packages/backend-app-api/src/services/implementations/tokenManagerService.ts new file mode 100644 index 0000000000..2e82fce9d9 --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/tokenManagerService.ts @@ -0,0 +1,60 @@ +/* + * 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 { + configServiceRef, + loggerServiceRef, + createServiceFactory, + tokenManagerServiceRef, + loggerToWinstonLogger, +} from '@backstage/backend-plugin-api'; +import { ServerTokenManager } from '@backstage/backend-common'; + +export const tokenManagerFactory = createServiceFactory({ + service: tokenManagerServiceRef, + deps: { + configFactory: configServiceRef, + loggerFactory: loggerServiceRef, + }, + factory: async ({ configFactory, loggerFactory }) => { + const logger = await loggerFactory('root'); + const config = await configFactory('root'); + return async (_pluginId: string) => { + // doesn't the logger want to be inferred from the plugin tho here? + // maybe ... also why do we recreate it every time otherwise + // we should memoize on a per plugin right? so I think it's should be fine to re-use the plugin one + // we shouldn't recreate on a per plugin basis. + // hm - on the other hand, is this really ever called more than once? + // not this function right. should only be called when the plugin requests this serviceRef + // yeah so no need to worry about memo probably + // but we still want to scope the logger to the ServrTokenmanagfer>? + // mm sure maybe + // maybe in this case it doesn't provide so much value b + // oh hang on - isn't it up to THE MANAGER to make a child internally if it wants to do that + // so that it becomes a property intrinsic to that class, no matter how it's constructed + // or is that too much responsibility for it - making the constructor complex so to speak, making it harder to tweak that behavior + // this is not ultra efficient :) + + // I think the naming here is wrong to be gonest + // this isn't like the cache manager or the database manager + // the manager name is confusuion i think + // aye perhaps + return ServerTokenManager.fromConfig(config, { + logger: loggerToWinstonLogger(logger), + }); + }; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/urlReaderService.ts b/packages/backend-app-api/src/services/implementations/urlReaderService.ts new file mode 100644 index 0000000000..74431dfa0d --- /dev/null +++ b/packages/backend-app-api/src/services/implementations/urlReaderService.ts @@ -0,0 +1,41 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { UrlReaders } from '@backstage/backend-common'; +import { + configServiceRef, + createServiceFactory, + loggerServiceRef, + loggerToWinstonLogger, + urlReaderServiceRef, +} from '@backstage/backend-plugin-api'; + +export const urlReaderFactory = createServiceFactory({ + service: urlReaderServiceRef, + deps: { + configFactory: configServiceRef, + loggerFactory: loggerServiceRef, + }, + factory: async ({ configFactory, loggerFactory }) => { + return async (pluginId: string) => { + const logger = await loggerFactory(pluginId); + return UrlReaders.default({ + logger: loggerToWinstonLogger(logger), + config: await configFactory(pluginId), + }); + }; + }, +}); diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts new file mode 100644 index 0000000000..ab086f4144 --- /dev/null +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -0,0 +1,155 @@ +/* + * 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 { + BackendRegistrable, + ExtensionPoint, + ServiceRef, +} from '@backstage/backend-plugin-api'; +import { BackendRegisterInit, ServiceHolder } from './types'; + +type ServiceOrExtensionPoint = ExtensionPoint | ServiceRef; + +export class BackendInitializer { + #started = false; + #extensions = new Map(); + #registerInits = new Array(); + #extensionPoints = new Map(); + #serviceHolder: ServiceHolder; + + constructor(serviceHolder: ServiceHolder) { + this.#serviceHolder = serviceHolder; + } + + async #getInitDeps( + deps: { [name: string]: ServiceOrExtensionPoint }, + pluginId: string, + ) { + return Object.fromEntries( + await Promise.all( + Object.entries(deps).map(async ([name, ref]) => [ + name, + this.#extensionPoints.get(ref) || + (await this.#serviceHolder.get(ref as ServiceRef)!( + pluginId, + )), + ]), + ), + ); + } + + add(extension: BackendRegistrable, options?: TOptions) { + if (this.#started) { + throw new Error( + 'extension can not be added after the backend has started', + ); + } + this.#extensions.set(extension, options); + } + + async start(): Promise { + console.log(`Starting backend`); + if (this.#started) { + throw new Error('Backend has already started'); + } + this.#started = true; + + for (const [extension] of this.#extensions) { + const provides = new Set>(); + + let registerInit: BackendRegisterInit | undefined = undefined; + + console.log('Registering', extension.id); + extension.register({ + registerExtensionPoint: (extensionPointRef, impl) => { + if (registerInit) { + throw new Error('registerExtensionPoint called after registerInit'); + } + if (this.#extensionPoints.has(extensionPointRef)) { + throw new Error(`API ${extensionPointRef.id} already registered`); + } + this.#extensionPoints.set(extensionPointRef, impl); + provides.add(extensionPointRef); + }, + registerInit: registerOptions => { + if (registerInit) { + throw new Error('registerInit must only be called once'); + } + registerInit = { + id: extension.id, + provides, + consumes: new Set(Object.values(registerOptions.deps)), + deps: registerOptions.deps, + init: registerOptions.init as BackendRegisterInit['init'], + }; + }, + }); + + if (!registerInit) { + throw new Error( + `registerInit was not called by register in ${extension.id}`, + ); + } + + this.#registerInits.push(registerInit); + } + + this.validateSetup(); + + const orderedRegisterResults = this.#resolveInitOrder(this.#registerInits); + + for (const registerInit of orderedRegisterResults) { + const deps = await this.#getInitDeps(registerInit.deps, registerInit.id); + await registerInit.init(deps); + } + } + + private validateSetup() {} + + #resolveInitOrder(registerInits: Array) { + let registerInitsToOrder = registerInits.slice(); + const orderedRegisterInits = new Array(); + + // TODO: Validate duplicates + + while (registerInitsToOrder.length > 0) { + const toRemove = new Set(); + + for (const registerInit of registerInitsToOrder) { + const unInitializedDependents = []; + + for (const serviceRef of registerInit.provides) { + if ( + registerInitsToOrder.some( + init => init !== registerInit && init.consumes.has(serviceRef), + ) + ) { + unInitializedDependents.push(serviceRef); + } + } + + if (unInitializedDependents.length === 0) { + console.log(`DEBUG: pushed ${registerInit.id} to results`); + orderedRegisterInits.push(registerInit); + toRemove.add(registerInit); + } + } + + registerInitsToOrder = registerInitsToOrder.filter(r => !toRemove.has(r)); + } + return orderedRegisterInits; + } +} diff --git a/packages/backend-app-api/src/wiring/BackstageBackend.ts b/packages/backend-app-api/src/wiring/BackstageBackend.ts new file mode 100644 index 0000000000..104d2b6fc8 --- /dev/null +++ b/packages/backend-app-api/src/wiring/BackstageBackend.ts @@ -0,0 +1,45 @@ +/* + * 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 { + AnyServiceFactory, + BackendRegistrable, +} 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: AnyServiceFactory[]) { + this.#services = new ServiceRegistry(apiFactories); + this.#initializer = new BackendInitializer(this.#services); + } + + add(extension: BackendRegistrable): void { + this.#initializer.add(extension); + } + + async start(): Promise { + await this.#initializer.start(); + } + + // async stop(): Promise { + // await this.#initializer.stop(); + // } +} diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.ts new file mode 100644 index 0000000000..bdaecf30c6 --- /dev/null +++ b/packages/backend-app-api/src/wiring/ServiceRegistry.ts @@ -0,0 +1,63 @@ +/* + * 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 { + AnyServiceFactory, + FactoryFunc, + ServiceRef, +} from '@backstage/backend-plugin-api'; + +export class ServiceRegistry { + readonly #implementations: Map>; + readonly #factories: Map; + + constructor(factories: AnyServiceFactory[]) { + this.#factories = new Map(factories.map(f => [f.service.id, f])); + this.#implementations = new Map(); + } + + get(ref: ServiceRef): FactoryFunc | undefined { + const factory = this.#factories.get(ref.id); + if (!factory) { + return undefined; + } + + return async (pluginId: string): Promise => { + let implementations = this.#implementations.get(ref.id); + if (implementations) { + if (implementations.has(pluginId)) { + return implementations.get(pluginId) as T; + } + } else { + implementations = new Map(); + this.#implementations.set(ref.id, implementations); + } + + const factoryDeps = Object.fromEntries( + Object.entries(factory.deps).map(([name, serviceRef]) => [ + name, + this.get(serviceRef)!, // TODO: throw + ]), + ); + + const factoryFunc = await factory.factory(factoryDeps); + const implementation = await factoryFunc(pluginId); + + implementations.set(pluginId, implementation); + + return implementation as T; + }; + } +} diff --git a/packages/backend-app-api/src/wiring/index.ts b/packages/backend-app-api/src/wiring/index.ts new file mode 100644 index 0000000000..1dd91e30e1 --- /dev/null +++ b/packages/backend-app-api/src/wiring/index.ts @@ -0,0 +1,18 @@ +/* + * 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 type { Backend, CreateBackendOptions } from './types'; +export { createBackend } from './types'; diff --git a/packages/backend-app-api/src/wiring/types.ts b/packages/backend-app-api/src/wiring/types.ts new file mode 100644 index 0000000000..112fe06b05 --- /dev/null +++ b/packages/backend-app-api/src/wiring/types.ts @@ -0,0 +1,62 @@ +/* + * 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 { + AnyServiceFactory, + BackendRegistrable, + FactoryFunc, + ServiceRef, +} from '@backstage/backend-plugin-api'; +import { defaultServiceFactories } from '../services/implementations'; +import { BackstageBackend } from './BackstageBackend'; + +/** + * @public + */ +export interface Backend { + add(extension: BackendRegistrable): void; + start(): Promise; +} + +export interface BackendRegisterInit { + id: string; + consumes: Set>; + provides: Set>; + deps: { [name: string]: ServiceRef }; + init: (deps: { [name: string]: unknown }) => Promise; +} + +/** + * @public + */ +export interface CreateBackendOptions { + apis: AnyServiceFactory[]; +} + +export type ServiceHolder = { + get(api: ServiceRef): FactoryFunc | undefined; +}; + +/** + * @public + */ +export function createBackend(options?: CreateBackendOptions): Backend { + // TODO: merge with provided APIs + return new BackstageBackend([ + ...defaultServiceFactories, + ...(options?.apis ?? []), + ]); +} diff --git a/packages/backend-next/.eslintrc.js b/packages/backend-next/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/backend-next/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/backend-next/README.md b/packages/backend-next/README.md new file mode 100644 index 0000000000..94fc0767c4 --- /dev/null +++ b/packages/backend-next/README.md @@ -0,0 +1,5 @@ +# example-backend-next + +This is an example backend for the new **EXPERIMENTAL** Backstage backend system. + +Do not use this in your own projects. diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json new file mode 100644 index 0000000000..673116790e --- /dev/null +++ b/packages/backend-next/package.json @@ -0,0 +1,38 @@ +{ + "name": "example-backend-next", + "version": "0.0.0", + "main": "dist/index.cjs.js", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "backstage": { + "role": "backend" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/backend-next" + }, + "keywords": [ + "backstage" + ], + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean" + }, + "dependencies": { + "@backstage/backend-app-api": "^0.0.0", + "@backstage/plugin-catalog-backend": "^1.2.1-next.2", + "@backstage/plugin-scaffolder-backend": "^1.4.0-next.2" + }, + "devDependencies": { + "@backstage/cli": "^0.18.0-next.2" + }, + "files": [ + "dist" + ] +} diff --git a/packages/backend-next/src/index.ts b/packages/backend-next/src/index.ts new file mode 100644 index 0000000000..6463dc7ed5 --- /dev/null +++ b/packages/backend-next/src/index.ts @@ -0,0 +1,27 @@ +/* + * 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 { createBackend } from '@backstage/backend-app-api'; +import { catalogPlugin } from '@backstage/plugin-catalog-backend'; +import { scaffolderCatalogModule } from '@backstage/plugin-scaffolder-backend'; + +const backend = createBackend({ + apis: [], +}); + +backend.add(catalogPlugin({})); +backend.add(scaffolderCatalogModule({})); +backend.start(); diff --git a/packages/backend-plugin-api/.eslintrc.js b/packages/backend-plugin-api/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/packages/backend-plugin-api/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/packages/backend-plugin-api/README.md b/packages/backend-plugin-api/README.md new file mode 100644 index 0000000000..364655dd60 --- /dev/null +++ b/packages/backend-plugin-api/README.md @@ -0,0 +1,19 @@ +# @backstage/backend-plugin-api + +**This package is HIGHLY EXPERIMENTAL, do not use this for production** + +This package provides the core API used by Backstage backend plugins. + +## Installation + +Add the library to your backend plugin package: + +```bash +# From your Backstage root directory +yarn add --cwd plugin/-backend @backstage/backend-plugin-api +``` + +## Documentation + +- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md new file mode 100644 index 0000000000..df8474d670 --- /dev/null +++ b/packages/backend-plugin-api/api-report.md @@ -0,0 +1,197 @@ +## 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 { Config } from '@backstage/config'; +import { Handler } from 'express'; +import { Logger as Logger_2 } from 'winston'; +import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; +import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +import { PluginCacheManager } from '@backstage/backend-common'; +import { PluginDatabaseManager } from '@backstage/backend-common'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { TokenManager } from '@backstage/backend-common'; +import { TransportStreamOptions } from 'winston-transport'; +import { UrlReader } from '@backstage/backend-common'; + +// @public (undocumented) +export type AnyServiceFactory = ServiceFactory< + unknown, + unknown, + { + [key in string]: unknown; + } +>; + +// @public (undocumented) +export interface BackendInitRegistry { + // (undocumented) + registerExtensionPoint( + ref: ServiceRef, + impl: TExtensionPoint, + ): void; + // (undocumented) + registerInit< + Deps extends { + [name in string]: unknown; + }, + >(options: { + deps: { + [name in keyof Deps]: ServiceRef; + }; + init: (deps: Deps) => Promise; + }): void; +} + +// @public (undocumented) +export interface BackendModuleConfig { + // (undocumented) + moduleId: string; + // (undocumented) + pluginId: string; + // (undocumented) + register( + reg: Omit, + options: TOptions, + ): void; +} + +// @public (undocumented) +export interface BackendPluginConfig { + // (undocumented) + id: string; + // (undocumented) + register(reg: BackendInitRegistry, options: TOptions): void; +} + +// @public (undocumented) +export interface BackendRegistrable { + // (undocumented) + id: string; + // (undocumented) + register(reg: BackendInitRegistry): void; +} + +// @public (undocumented) +export const cacheServiceRef: ServiceRef; + +// @public (undocumented) +export const configServiceRef: ServiceRef; + +// @public (undocumented) +export function createBackendModule( + config: BackendModuleConfig, +): (option: TOptions) => BackendRegistrable; + +// @public (undocumented) +export function createBackendPlugin( + config: BackendPluginConfig, +): (option: TOptions) => BackendRegistrable; + +// @public (undocumented) +export function createExtensionPoint(options: { + id: string; +}): ExtensionPoint; + +// @public (undocumented) +export function createServiceFactory< + Api, + Impl extends Api, + Deps extends { + [name in string]: unknown; + }, +>(factory: ServiceFactory): ServiceFactory; + +// @public (undocumented) +export function createServiceRef(options: { id: string }): ServiceRef; + +// @public (undocumented) +export const databaseServiceRef: ServiceRef; + +// @public (undocumented) +export type DepsToDepFactories = { + [key in keyof T]: (pluginId: string) => Promise; +}; + +// @public (undocumented) +export const discoveryServiceRef: ServiceRef; + +// @public +export type ExtensionPoint = { + id: string; + T: T; + toString(): string; + $$ref: 'extension-point'; +}; + +// @public (undocumented) +export type FactoryFunc = (pluginId: string) => Promise; + +// @public (undocumented) +export interface HttpRouterService { + // (undocumented) + use(handler: Handler): void; +} + +// @public (undocumented) +export const httpRouterServiceRef: ServiceRef; + +// @public (undocumented) +export interface Logger { + // (undocumented) + child(fields: { [name: string]: string }): Logger; + // (undocumented) + info(message: string): void; +} + +// @public (undocumented) +export const loggerServiceRef: ServiceRef; + +// @public (undocumented) +export function loggerToWinstonLogger( + logger: Logger, + opts?: TransportStreamOptions, +): Logger_2; + +// @public (undocumented) +export const permissionsServiceRef: ServiceRef< + PermissionAuthorizer | PermissionEvaluator +>; + +// @public (undocumented) +export const schedulerServiceRef: ServiceRef; + +// @public (undocumented) +export type ServiceFactory< + TApi, + TImpl extends TApi, + TDeps extends { + [name in string]: unknown; + }, +> = { + service: ServiceRef; + deps: TypesToServiceRef; + factory(deps: DepsToDepFactories): Promise>; +}; + +// @public +export type ServiceRef = { + id: string; + T: T; + toString(): string; + $$ref: 'service'; +}; + +// @public (undocumented) +export const tokenManagerServiceRef: ServiceRef; + +// @public (undocumented) +export type TypesToServiceRef = { + [key in keyof T]: ServiceRef; +}; + +// @public (undocumented) +export const urlReaderServiceRef: ServiceRef; +``` diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json new file mode 100644 index 0000000000..f9109ae680 --- /dev/null +++ b/packages/backend-plugin-api/package.json @@ -0,0 +1,53 @@ +{ + "name": "@backstage/backend-plugin-api", + "description": "Core API used by Backstage backend plugins", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts", + "alphaTypes": "dist/index.alpha.d.ts" + }, + "backstage": { + "role": "node-library" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/backend-plugin-api" + }, + "keywords": [ + "backstage" + ], + "license": "Apache-2.0", + "scripts": { + "build": "backstage-cli package build --experimental-type-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", + "start": "backstage-cli package start" + }, + "dependencies": { + "@backstage/config": "^1.0.1", + "@backstage/backend-common": "^0.14.1-next.2", + "@backstage/plugin-permission-common": "^0.6.3-next.0", + "@backstage/backend-tasks": "^0.3.3-next.2", + "@types/express": "^4.17.6", + "express": "^4.17.1", + "winston": "^3.2.1", + "winston-transport": "^4.5.0" + }, + "devDependencies": { + "@backstage/cli": "^0.18.0-next.2" + }, + "files": [ + "dist", + "alpha" + ] +} diff --git a/packages/backend-plugin-api/src/index.ts b/packages/backend-plugin-api/src/index.ts new file mode 100644 index 0000000000..452fa046f9 --- /dev/null +++ b/packages/backend-plugin-api/src/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Core API used by Backstage backend plugins. + * + * @packageDocumentation + */ + +export * from './services'; +export * from './wiring'; diff --git a/packages/backend-plugin-api/src/services/definitions/cacheServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/cacheServiceRef.ts new file mode 100644 index 0000000000..0b572f1712 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/cacheServiceRef.ts @@ -0,0 +1,25 @@ +/* + * 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 { createServiceRef } from '../system/types'; +import { PluginCacheManager } from '@backstage/backend-common'; + +/** + * @public + */ +export const cacheServiceRef = createServiceRef({ + id: 'core.cache', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/configServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/configServiceRef.ts new file mode 100644 index 0000000000..ba5dcacdc8 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/configServiceRef.ts @@ -0,0 +1,25 @@ +/* + * 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 { Config } from '@backstage/config'; +import { createServiceRef } from '../system/types'; + +/** + * @public + */ +export const configServiceRef = createServiceRef({ + id: 'core.config', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/databaseServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/databaseServiceRef.ts new file mode 100644 index 0000000000..b41159dc9e --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/databaseServiceRef.ts @@ -0,0 +1,25 @@ +/* + * 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 { PluginDatabaseManager } from '@backstage/backend-common'; +import { createServiceRef } from '../system/types'; + +/** + * @public + */ +export const databaseServiceRef = createServiceRef({ + id: 'core.database', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/discoveryServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/discoveryServiceRef.ts new file mode 100644 index 0000000000..675ac206a2 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/discoveryServiceRef.ts @@ -0,0 +1,25 @@ +/* + * 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 { createServiceRef } from '../system/types'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; + +/** + * @public + */ +export const discoveryServiceRef = createServiceRef({ + id: 'core.discovery', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/httpRouterServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/httpRouterServiceRef.ts new file mode 100644 index 0000000000..fbc5a27e87 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/httpRouterServiceRef.ts @@ -0,0 +1,32 @@ +/* + * 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 { createServiceRef } from '../system/types'; +import { Handler } from 'express'; + +/** + * @public + */ +export interface HttpRouterService { + use(handler: Handler): void; +} + +/** + * @public + */ +export const httpRouterServiceRef = createServiceRef({ + id: 'core.httpRouter', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts new file mode 100644 index 0000000000..797cfb5a76 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -0,0 +1,28 @@ +/* + * 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 { configServiceRef } from './configServiceRef'; +export { httpRouterServiceRef } from './httpRouterServiceRef'; +export type { HttpRouterService } from './httpRouterServiceRef'; +export { loggerServiceRef } from './loggerServiceRef'; +export type { Logger } from './loggerServiceRef'; +export { urlReaderServiceRef } from './urlReaderServiceRef'; +export { cacheServiceRef } from './cacheServiceRef'; +export { databaseServiceRef } from './databaseServiceRef'; +export { discoveryServiceRef } from './discoveryServiceRef'; +export { tokenManagerServiceRef } from './tokenManagerServiceRef'; +export { permissionsServiceRef } from './permissionsServiceRef'; +export { schedulerServiceRef } from './schedulerServiceRef'; diff --git a/packages/backend-plugin-api/src/services/definitions/loggerServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/loggerServiceRef.ts new file mode 100644 index 0000000000..e99bc4e7d4 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/loggerServiceRef.ts @@ -0,0 +1,32 @@ +/* + * 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 { createServiceRef } from '../system/types'; + +/** + * @public + */ +export interface Logger { + info(message: string): void; + child(fields: { [name: string]: string }): Logger; +} + +/** + * @public + */ +export const loggerServiceRef = createServiceRef({ + id: 'core.logger', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/permissionsServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/permissionsServiceRef.ts new file mode 100644 index 0000000000..b13ca9e240 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/permissionsServiceRef.ts @@ -0,0 +1,30 @@ +/* + * 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 { createServiceRef } from '../system/types'; +import { + PermissionAuthorizer, + PermissionEvaluator, +} from '@backstage/plugin-permission-common'; + +/** + * @public + */ +export const permissionsServiceRef = createServiceRef< + PermissionEvaluator | PermissionAuthorizer +>({ + id: 'core.permissions', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/schedulerServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/schedulerServiceRef.ts new file mode 100644 index 0000000000..ca442dd34d --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/schedulerServiceRef.ts @@ -0,0 +1,25 @@ +/* + * 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 { createServiceRef } from '../system/types'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; + +/** + * @public + */ +export const schedulerServiceRef = createServiceRef({ + id: 'core.scheduler', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/tokenManagerServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/tokenManagerServiceRef.ts new file mode 100644 index 0000000000..6995033e81 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/tokenManagerServiceRef.ts @@ -0,0 +1,25 @@ +/* + * 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 { createServiceRef } from '../system/types'; +import { TokenManager } from '@backstage/backend-common'; + +/** + * @public + */ +export const tokenManagerServiceRef = createServiceRef({ + id: 'core.tokenManager', +}); diff --git a/packages/backend-plugin-api/src/services/definitions/urlReaderServiceRef.ts b/packages/backend-plugin-api/src/services/definitions/urlReaderServiceRef.ts new file mode 100644 index 0000000000..ebb32f1aed --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/urlReaderServiceRef.ts @@ -0,0 +1,25 @@ +/* + * 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 { createServiceRef } from '../system/types'; +import { UrlReader } from '@backstage/backend-common'; + +/** + * @public + */ +export const urlReaderServiceRef = createServiceRef({ + id: 'core.urlReader', +}); diff --git a/packages/backend-plugin-api/src/services/helpers/index.ts b/packages/backend-plugin-api/src/services/helpers/index.ts new file mode 100644 index 0000000000..24336c1af2 --- /dev/null +++ b/packages/backend-plugin-api/src/services/helpers/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 { loggerToWinstonLogger } from './loggerToWinstonLogger'; diff --git a/packages/backend-plugin-api/src/services/helpers/loggerToWinstonLogger.ts b/packages/backend-plugin-api/src/services/helpers/loggerToWinstonLogger.ts new file mode 100644 index 0000000000..b9ccd70d3f --- /dev/null +++ b/packages/backend-plugin-api/src/services/helpers/loggerToWinstonLogger.ts @@ -0,0 +1,44 @@ +/* + * 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 { Logger as BackstageLogger } from '../definitions'; +import { Logger as WinstonLogger, createLogger } from 'winston'; +import Transport, { TransportStreamOptions } from 'winston-transport'; + +class BackstageLoggerTransport extends Transport { + constructor( + private readonly backstageLogger: BackstageLogger, + opts?: TransportStreamOptions, + ) { + super(opts); + } + + log(info: { message: string }, callback: VoidFunction) { + // TODO: add support for levels and fields + this.backstageLogger.info(info.message); + callback(); + } +} + +/** @public */ +export function loggerToWinstonLogger( + logger: BackstageLogger, + opts?: TransportStreamOptions, +): WinstonLogger { + return createLogger({ + transports: [new BackstageLoggerTransport(logger, opts)], + }); +} diff --git a/packages/backend-plugin-api/src/services/index.ts b/packages/backend-plugin-api/src/services/index.ts new file mode 100644 index 0000000000..96bd3d320a --- /dev/null +++ b/packages/backend-plugin-api/src/services/index.ts @@ -0,0 +1,19 @@ +/* + * 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 * from './definitions'; +export * from './helpers'; +export * from './system'; diff --git a/packages/backend-plugin-api/src/services/system/index.ts b/packages/backend-plugin-api/src/services/system/index.ts new file mode 100644 index 0000000000..eead2297a5 --- /dev/null +++ b/packages/backend-plugin-api/src/services/system/index.ts @@ -0,0 +1,25 @@ +/* + * 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 type { + ServiceRef, + TypesToServiceRef, + DepsToDepFactories, + FactoryFunc, + ServiceFactory, + AnyServiceFactory, +} from './types'; +export { createServiceRef, createServiceFactory } from './types'; diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts new file mode 100644 index 0000000000..d578434170 --- /dev/null +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -0,0 +1,90 @@ +/* + * 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. + */ + +/** + * TODO + * + * @public + */ +export type ServiceRef = { + id: string; + + /** + * Utility for getting the type of the service, using `typeof serviceRef.T`. + * Attempting to actually read this value will result in an exception. + */ + T: T; + + toString(): string; + + $$ref: 'service'; +}; + +/** @public */ +export type TypesToServiceRef = { [key in keyof T]: ServiceRef }; + +/** @public */ +export type DepsToDepFactories = { + [key in keyof T]: (pluginId: string) => Promise; +}; + +/** @public */ +export type FactoryFunc = (pluginId: string) => Promise; + +/** @public */ +export type ServiceFactory< + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, +> = { + service: ServiceRef; + deps: TypesToServiceRef; + factory(deps: DepsToDepFactories): Promise>; +}; + +/** @public */ +export type AnyServiceFactory = ServiceFactory< + unknown, + unknown, + { [key in string]: unknown } +>; + +/** + * @public + */ +export function createServiceRef(options: { id: string }): ServiceRef { + return { + id: options.id, + get T(): T { + throw new Error(`tried to read ServiceRef.T of ${this}`); + }, + toString() { + return `serviceRef{${options.id}}`; + }, + $$ref: 'service', // TODO: declare + }; +} + +/** + * @public + */ +export function createServiceFactory< + Api, + Impl extends Api, + Deps extends { [name in string]: unknown }, +>(factory: ServiceFactory): ServiceFactory { + return factory; +} diff --git a/packages/backend-plugin-api/src/wiring/index.ts b/packages/backend-plugin-api/src/wiring/index.ts new file mode 100644 index 0000000000..9340dc8fa9 --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/index.ts @@ -0,0 +1,18 @@ +/* + * 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 type { BackendRegistrable } from './types'; +export * from './types'; diff --git a/packages/backend-plugin-api/src/wiring/types.ts b/packages/backend-plugin-api/src/wiring/types.ts new file mode 100644 index 0000000000..2b0bef16f7 --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/types.ts @@ -0,0 +1,113 @@ +/* + * 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 { ServiceRef } from '../services/system/types'; + +/** + * TODO + * + * @public + */ +export type ExtensionPoint = { + id: string; + + /** + * Utility for getting the type of the extension point, using `typeof extensionPoint.T`. + * Attempting to actually read this value will result in an exception. + */ + T: T; + + toString(): string; + + $$ref: 'extension-point'; +}; + +/** @public */ +export function createExtensionPoint(options: { + id: string; +}): ExtensionPoint { + return { + id: options.id, + get T(): T { + throw new Error(`tried to read ExtensionPoint.T of ${this}`); + }, + toString() { + return `extensionPoint{${options.id}}`; + }, + $$ref: 'extension-point', // TODO: declare + }; +} + +/** @public */ +export interface BackendInitRegistry { + registerExtensionPoint( + ref: ServiceRef, + impl: TExtensionPoint, + ): void; + registerInit(options: { + deps: { [name in keyof Deps]: ServiceRef }; + init: (deps: Deps) => Promise; + }): void; +} + +/** @public */ +export interface BackendRegistrable { + id: string; + register(reg: BackendInitRegistry): void; +} + +/** @public */ +export interface BackendPluginConfig { + id: string; + register(reg: BackendInitRegistry, options: TOptions): void; +} + +// TODO: Make option optional in the returned factory if they are indeed optional +/** @public */ +export function createBackendPlugin( + config: BackendPluginConfig, +): (option: TOptions) => BackendRegistrable { + return options => ({ + id: config.id, + register(register) { + return config.register(register, options); + }, + }); +} + +/** @public */ +export interface BackendModuleConfig { + pluginId: string; + moduleId: string; + register( + reg: Omit, + options: TOptions, + ): void; +} + +// TODO: Make option optional in the returned factory if they are indeed optional +/** @public */ +export function createBackendModule( + config: BackendModuleConfig, +): (option: TOptions) => BackendRegistrable { + return options => ({ + id: `${config.pluginId}.${config.moduleId}`, + register(register) { + // TODO: Hide registerExtensionPoint + return config.register(register, options); + }, + }); +} diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index a58fdcf4d5..460dafa43a 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -47,7 +47,7 @@ "@types/jest": "^26.0.7", "@types/json-schema": "^7.0.5", "@types/lodash": "^4.14.151", - "yaml": "^1.9.2" + "yaml": "^2.0.0" }, "files": [ "dist", diff --git a/packages/cli/package.json b/packages/cli/package.json index 6b8e1feb97..d64ccf0c7d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -120,7 +120,7 @@ "webpack": "^5.66.0", "webpack-dev-server": "^4.7.3", "webpack-node-externals": "^3.0.0", - "yaml": "^1.10.0", + "yaml": "^2.0.0", "yml-loader": "^2.1.0", "yn": "^4.0.0", "zod": "^3.11.6" diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 3ca4b1455f..638e3dc8e0 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -47,7 +47,7 @@ "json-schema-traverse": "^1.0.0", "node-fetch": "^2.6.7", "typescript-json-schema": "^0.54.0", - "yaml": "^1.9.2", + "yaml": "^2.0.0", "yup": "^0.32.9" }, "devDependencies": { diff --git a/packages/config-loader/src/lib/transform/include.test.ts b/packages/config-loader/src/lib/transform/include.test.ts index 91df723621..0913b31f66 100644 --- a/packages/config-loader/src/lib/transform/include.test.ts +++ b/packages/config-loader/src/lib/transform/include.test.ts @@ -155,7 +155,7 @@ describe('includeTransform', () => { await expect( includeTransform({ $include: 'invalid.yaml' }, root), ).rejects.toThrow( - 'failed to parse included file invalid.yaml, YAMLSyntaxError: Flow sequence contains an unexpected }', + /failed to parse included file invalid.yaml, YAMLParseError: Flow sequence in block collection must be sufficiently indented and end with a \] at line 1, column 7:\s+foo: \[\}/, ); }); diff --git a/plugins/adr/package.json b/plugins/adr/package.json index 80d24e7223..9f25ce4c61 100644 --- a/plugins/adr/package.json +++ b/plugins/adr/package.json @@ -34,7 +34,7 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "git-url-parse": "^12.0.0", - "octokit": "^1.7.1", + "octokit": "^2.0.0", "react-markdown": "^8.0.0", "react-router-dom": "6.0.0-beta.0", "react-use": "^17.2.4", diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index c768bbe9e4..4b4e60cf4d 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -51,7 +51,7 @@ "@backstage/cli": "^0.18.0-next.1", "@types/lodash": "^4.14.151", "aws-sdk-mock": "^5.2.1", - "yaml": "^1.9.2" + "yaml": "^2.0.0" }, "files": [ "dist", diff --git a/plugins/catalog-backend-module-ldap/src/ldap/client.ts b/plugins/catalog-backend-module-ldap/src/ldap/client.ts index a9283ecb94..d9d0faf9a2 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/client.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/client.ts @@ -23,6 +23,7 @@ import { errorString } from './util'; import { ActiveDirectoryVendor, DefaultLdapVendor, + FreeIpaVendor, LdapVendor, } from './vendors'; @@ -199,6 +200,8 @@ export class LdapClient { .then(root => { if (root && root.raw?.forestFunctionality) { return ActiveDirectoryVendor; + } else if (root && root.raw?.ipaDomainLevel) { + return FreeIpaVendor; } return DefaultLdapVendor; }) diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts index 49803b9c39..fc451fde97 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts @@ -32,7 +32,11 @@ import { resolveRelations, } from './read'; import { RecursivePartial } from './util'; -import { ActiveDirectoryVendor, DefaultLdapVendor } from './vendors'; +import { + ActiveDirectoryVendor, + DefaultLdapVendor, + FreeIpaVendor, +} from './vendors'; function user(data: RecursivePartial): UserEntity { return merge( @@ -195,6 +199,62 @@ describe('readLdapUsers', () => { new Map([['dn-value', new Set(['x', 'y', 'z'])]]), ); }); + + it('transfers all attributes from FreeIPA', async () => { + client.getVendor.mockResolvedValue(FreeIpaVendor); + client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { + await fn( + searchEntry({ + uid: ['uid-value'], + description: ['description-value'], + cn: ['cn-value'], + mail: ['mail-value'], + avatarUrl: ['avatarUrl-value'], + memberOf: ['x', 'y', 'z'], + dn: ['dn-value'], + ipaUniqueID: ['uuid-value'], + }), + ); + }); + const config: UserConfig = { + dn: 'ddd', + options: {}, + map: { + rdn: 'uid', + name: 'uid', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + memberOf: 'memberOf', + }, + }; + const { users, userMemberOf } = await readLdapUsers(client, config); + expect(users).toEqual([ + expect.objectContaining({ + metadata: { + name: 'uid-value', + description: 'description-value', + annotations: { + [LDAP_DN_ANNOTATION]: 'dn-value', + [LDAP_RDN_ANNOTATION]: 'uid-value', + [LDAP_UUID_ANNOTATION]: 'uuid-value', + }, + }, + spec: { + profile: { + displayName: 'cn-value', + email: 'mail-value', + picture: 'avatarUrl-value', + }, + memberOf: [], + }, + }), + ]); + expect(userMemberOf).toEqual( + new Map([['dn-value', new Set(['x', 'y', 'z'])]]), + ); + }); }); describe('readLdapGroups', () => { diff --git a/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts b/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts index 447df8aaa5..d2df250638 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/vendors.ts @@ -63,6 +63,16 @@ export const ActiveDirectoryVendor: LdapVendor = { }, }; +export const FreeIpaVendor: LdapVendor = { + dnAttributeName: 'dn', + uuidAttributeName: 'ipaUniqueID', + decodeStringAttribute: (entry, name) => { + return decode(entry, name, value => { + return value.toString(); + }); + }, +}; + // Decode an attribute to a consumer function decode( entry: SearchEntry, diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 09ca4d8dfe..22a7f8192c 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -5,18 +5,34 @@ ```ts /// +import { BackendRegistrable } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { CatalogEntityDocument } from '@backstage/plugin-catalog-common'; -import { CompoundEntityRef } from '@backstage/catalog-model'; +import { CatalogProcessor } from '@backstage/plugin-catalog-node'; +import { CatalogProcessorCache } from '@backstage/plugin-catalog-node'; +import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node'; +import { CatalogProcessorEntityResult } from '@backstage/plugin-catalog-node'; +import { CatalogProcessorErrorResult } from '@backstage/plugin-catalog-node'; +import { CatalogProcessorLocationResult } from '@backstage/plugin-catalog-node'; +import { CatalogProcessorParser } from '@backstage/plugin-catalog-node'; +import { CatalogProcessorRefreshKeysResult } from '@backstage/plugin-catalog-node'; +import { CatalogProcessorRelationResult } from '@backstage/plugin-catalog-node'; +import { CatalogProcessorResult } from '@backstage/plugin-catalog-node'; import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common'; import { Conditions } from '@backstage/plugin-permission-node'; import { Config } from '@backstage/config'; +import { DeferredEntity } from '@backstage/plugin-catalog-node'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { Entity } from '@backstage/catalog-model'; import { EntityPolicy } from '@backstage/catalog-model'; +import { EntityProvider } from '@backstage/plugin-catalog-node'; +import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; +import { EntityProviderMutation } from '@backstage/plugin-catalog-node'; +import { EntityRelationSpec } from '@backstage/plugin-catalog-node'; import { GetEntitiesRequest } from '@backstage/catalog-client'; import { JsonValue } from '@backstage/types'; import { LocationEntityV1alpha1 } from '@backstage/catalog-model'; +import { LocationSpec } from '@backstage/plugin-catalog-node'; import { Logger } from 'winston'; import { Permission } from '@backstage/plugin-permission-common'; import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; @@ -26,6 +42,7 @@ import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { processingResult } from '@backstage/plugin-catalog-node'; import { Readable } from 'stream'; import { ResourcePermission } from '@backstage/plugin-permission-common'; import { Router } from 'express'; @@ -144,6 +161,13 @@ export class CatalogBuilder { processingInterval: ProcessingIntervalFunction, ): CatalogBuilder; setProcessingIntervalSeconds(seconds: number): CatalogBuilder; + // (undocumented) + subscribe(options: { + onProcessingError: (event: { + unprocessedEntity: Entity; + errors: Error[]; + }) => Promise | void; + }): void; } // @alpha @@ -199,6 +223,9 @@ export type CatalogEnvironment = { export type CatalogPermissionRule = PermissionRule; +// @alpha +export const catalogPlugin: (option: unknown) => BackendRegistrable; + // @public (undocumented) export interface CatalogProcessingEngine { // (undocumented) @@ -207,86 +234,25 @@ export interface CatalogProcessingEngine { stop(): Promise; } -// @public (undocumented) -export type CatalogProcessor = { - getProcessorName(): string; - readLocation?( - location: LocationSpec, - optional: boolean, - emit: CatalogProcessorEmit, - parser: CatalogProcessorParser, - cache: CatalogProcessorCache, - ): Promise; - preProcessEntity?( - entity: Entity, - location: LocationSpec, - emit: CatalogProcessorEmit, - originLocation: LocationSpec, - cache: CatalogProcessorCache, - ): Promise; - validateEntityKind?(entity: Entity): Promise; - postProcessEntity?( - entity: Entity, - location: LocationSpec, - emit: CatalogProcessorEmit, - cache: CatalogProcessorCache, - ): Promise; -}; +export { CatalogProcessor }; -// @public -export interface CatalogProcessorCache { - get(key: string): Promise; - set(key: string, value: ItemType): Promise; -} +export { CatalogProcessorCache }; -// @public (undocumented) -export type CatalogProcessorEmit = (generated: CatalogProcessorResult) => void; +export { CatalogProcessorEmit }; -// @public (undocumented) -export type CatalogProcessorEntityResult = { - type: 'entity'; - entity: Entity; - location: LocationSpec; -}; +export { CatalogProcessorEntityResult }; -// @public (undocumented) -export type CatalogProcessorErrorResult = { - type: 'error'; - error: Error; - location: LocationSpec; -}; +export { CatalogProcessorErrorResult }; -// @public (undocumented) -export type CatalogProcessorLocationResult = { - type: 'location'; - location: LocationSpec; -}; +export { CatalogProcessorLocationResult }; -// @public -export type CatalogProcessorParser = (options: { - data: Buffer; - location: LocationSpec; -}) => AsyncIterable; +export { CatalogProcessorParser }; -// @public (undocumented) -export type CatalogProcessorRefreshKeysResult = { - type: 'refresh'; - key: string; -}; +export { CatalogProcessorRefreshKeysResult }; -// @public (undocumented) -export type CatalogProcessorRelationResult = { - type: 'relation'; - relation: EntityRelationSpec; -}; +export { CatalogProcessorRelationResult }; -// @public (undocumented) -export type CatalogProcessorResult = - | CatalogProcessorLocationResult - | CatalogProcessorEntityResult - | CatalogProcessorRelationResult - | CatalogProcessorErrorResult - | CatalogProcessorRefreshKeysResult; +export { CatalogProcessorResult }; // @public (undocumented) export class CodeOwnersProcessor implements CatalogProcessor { @@ -394,11 +360,7 @@ export type DefaultCatalogCollatorFactoryOptions = { catalogClient?: CatalogApi; }; -// @public -export type DeferredEntity = { - entity: Entity; - locationKey?: string; -}; +export { DeferredEntity }; // @public export type EntitiesSearchFilter = { @@ -419,35 +381,13 @@ export type EntityFilter = } | EntitiesSearchFilter; -// @public -export interface EntityProvider { - connect(connection: EntityProviderConnection): Promise; - getProviderName(): string; -} +export { EntityProvider }; -// @public -export interface EntityProviderConnection { - applyMutation(mutation: EntityProviderMutation): Promise; -} +export { EntityProviderConnection }; -// @public -export type EntityProviderMutation = - | { - type: 'full'; - entities: DeferredEntity[]; - } - | { - type: 'delta'; - added: DeferredEntity[]; - removed: DeferredEntity[]; - }; +export { EntityProviderMutation }; -// @public -export type EntityRelationSpec = { - source: CompoundEntityRef; - type: string; - target: CompoundEntityRef; -}; +export { EntityRelationSpec }; // @public (undocumented) export class FileReaderProcessor implements CatalogProcessor { @@ -487,12 +427,7 @@ export type LocationEntityProcessorOptions = { integrations: ScmIntegrationRegistry; }; -// @public -export type LocationSpec = { - type: string; - target: string; - presence?: 'optional' | 'required'; -}; +export { LocationSpec }; // @public (undocumented) export function locationSpecToLocationEntity(opts: { @@ -593,28 +528,7 @@ export type PlaceholderResolverResolveUrl = ( // @public export type ProcessingIntervalFunction = () => number; -// @public -export const processingResult: Readonly<{ - readonly notFoundError: ( - atLocation: LocationSpec, - message: string, - ) => CatalogProcessorResult; - readonly inputError: ( - atLocation: LocationSpec, - message: string, - ) => CatalogProcessorResult; - readonly generalError: ( - atLocation: LocationSpec, - message: string, - ) => CatalogProcessorResult; - readonly location: (newLocation: LocationSpec) => CatalogProcessorResult; - readonly entity: ( - atLocation: LocationSpec, - newEntity: Entity, - ) => CatalogProcessorResult; - readonly relation: (spec: EntityRelationSpec) => CatalogProcessorResult; - readonly refresh: (key: string) => CatalogProcessorResult; -}>; +export { processingResult }; // @public (undocumented) export class UrlReaderProcessor implements CatalogProcessor { diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index ebed76ca90..64ad601b78 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -34,6 +34,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { + "@backstage/backend-plugin-api": "^0.0.0", + "@backstage/plugin-catalog-node": "^0.0.0", "@backstage/backend-common": "^0.14.1-next.2", "@backstage/catalog-client": "^1.0.4-next.1", "@backstage/catalog-model": "^1.1.0-next.2", @@ -63,7 +65,7 @@ "prom-client": "^14.0.1", "uuid": "^8.0.0", "winston": "^3.2.1", - "yaml": "^1.9.2", + "yaml": "^2.0.0", "yn": "^4.0.0", "zod": "^3.11.6" }, diff --git a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts index 903dd59e9e..c9b71d096d 100644 --- a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts +++ b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts @@ -35,7 +35,6 @@ import { ListParentsResult, RefreshByKeyOptions, } from './types'; -import { DeferredEntity } from '../processing/types'; import { ProcessingIntervalFunction } from '../processing/refresh'; import { rethrowError, timestampToDateTime } from './conversion'; import { initDatabaseMetrics } from './metrics'; @@ -48,6 +47,7 @@ import { import { generateStableHash } from './util'; import { isDatabaseConflictError } from '@backstage/backend-common'; +import { DeferredEntity } from '@backstage/plugin-catalog-node'; // The number of items that are sent per batch to the database layer, when // doing .batchInsert calls to knex. This needs to be low enough to not cause diff --git a/plugins/catalog-backend/src/database/types.ts b/plugins/catalog-backend/src/database/types.ts index 8a839c0650..07241f0682 100644 --- a/plugins/catalog-backend/src/database/types.ts +++ b/plugins/catalog-backend/src/database/types.ts @@ -17,9 +17,12 @@ import { Entity } from '@backstage/catalog-model'; import { JsonObject } from '@backstage/types'; import { DateTime } from 'luxon'; -import { EntityRelationSpec } from '../api'; -import { DeferredEntity, RefreshKeyData } from '../processing/types'; +import { + EntityRelationSpec, + DeferredEntity, +} from '@backstage/plugin-catalog-node'; import { DbRelationsRow } from './tables'; +import { RefreshKeyData } from '../processing/types'; /** * An abstraction for transactions of the underlying database technology. diff --git a/plugins/catalog-backend/src/index.ts b/plugins/catalog-backend/src/index.ts index c5c653f0dd..88696498d0 100644 --- a/plugins/catalog-backend/src/index.ts +++ b/plugins/catalog-backend/src/index.ts @@ -20,7 +20,26 @@ * @packageDocumentation */ -export * from './api'; +export type { + DeferredEntity, + LocationSpec, + EntityRelationSpec, + CatalogProcessor, + CatalogProcessorParser, + CatalogProcessorCache, + CatalogProcessorEmit, + CatalogProcessorLocationResult, + CatalogProcessorEntityResult, + CatalogProcessorRelationResult, + CatalogProcessorErrorResult, + CatalogProcessorRefreshKeysResult, + CatalogProcessorResult, + EntityProvider, + EntityProviderConnection, + EntityProviderMutation, +} from '@backstage/plugin-catalog-node'; +export { processingResult } from '@backstage/plugin-catalog-node'; + export * from './catalog'; export * from './ingestion'; export * from './modules'; diff --git a/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts b/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts index 13430f51dd..2592b8b94b 100644 --- a/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts +++ b/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts @@ -17,7 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { DefaultCatalogRulesEnforcer } from './CatalogRules'; -import { LocationSpec } from '../api'; +import { LocationSpec } from '@backstage/plugin-catalog-node'; const entity = { user: { diff --git a/plugins/catalog-backend/src/ingestion/CatalogRules.ts b/plugins/catalog-backend/src/ingestion/CatalogRules.ts index 587a0bd7d5..ce226bd09d 100644 --- a/plugins/catalog-backend/src/ingestion/CatalogRules.ts +++ b/plugins/catalog-backend/src/ingestion/CatalogRules.ts @@ -17,7 +17,7 @@ import { Config } from '@backstage/config'; import { Entity } from '@backstage/catalog-model'; import path from 'path'; -import { LocationSpec } from '../api'; +import { LocationSpec } from '@backstage/plugin-catalog-node'; /** * Rules to apply to catalog entities. diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 6d1e9a3927..2602bd080f 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -16,7 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { RecursivePartial } from '../util/RecursivePartial'; -import { LocationSpec } from '../api'; +import { LocationSpec } from '@backstage/plugin-catalog-node'; /** @public */ export type LocationAnalyzer = { diff --git a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts index ac4415549e..71154c5473 100644 --- a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts @@ -17,7 +17,7 @@ import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { CodeOwnersProcessor } from './CodeOwnersProcessor'; -import { LocationSpec } from '../../api'; +import { LocationSpec } from '@backstage/plugin-catalog-node'; const mockCodeOwnersText = () => ` * @acme/team-foo @acme/team-bar diff --git a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.ts b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.ts index 6f6aee7161..63f3d406f5 100644 --- a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.ts +++ b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.ts @@ -22,7 +22,7 @@ import { ScmIntegrations, } from '@backstage/integration'; import { Logger } from 'winston'; -import { CatalogProcessor, LocationSpec } from '../../api'; +import { CatalogProcessor, LocationSpec } from '@backstage/plugin-catalog-node'; import { findCodeOwnerByTarget } from './lib'; const ALLOWED_KINDS = ['API', 'Component', 'Domain', 'Resource', 'System']; diff --git a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.test.ts b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.test.ts index 3911f7f743..228429c647 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.test.ts @@ -17,7 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; -import { LocationSpec } from '../../api'; +import { LocationSpec } from '@backstage/plugin-catalog-node'; import { AnnotateLocationEntityProcessor } from './AnnotateLocationEntityProcessor'; describe('AnnotateLocationEntityProcessor', () => { diff --git a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts index 4af0b8cf61..a86525429e 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateLocationEntityProcessor.ts @@ -29,7 +29,7 @@ import { CatalogProcessor, CatalogProcessorEmit, LocationSpec, -} from '../../api'; +} from '@backstage/plugin-catalog-node'; /** @public */ export class AnnotateLocationEntityProcessor implements CatalogProcessor { diff --git a/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.test.ts b/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.test.ts index 7fd6514bb8..6154d82330 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.test.ts @@ -16,7 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { AnnotateScmSlugEntityProcessor } from './AnnotateScmSlugEntityProcessor'; -import { LocationSpec } from '../../api'; +import { LocationSpec } from '@backstage/plugin-catalog-node'; describe('AnnotateScmSlugEntityProcessor', () => { describe('github', () => { diff --git a/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.ts index c4f7218896..41e75e5e8e 100644 --- a/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/AnnotateScmSlugEntityProcessor.ts @@ -21,7 +21,7 @@ import { } from '@backstage/integration'; import parseGitUrl from 'git-url-parse'; import { identity, merge, pickBy } from 'lodash'; -import { CatalogProcessor, LocationSpec } from '../../api'; +import { CatalogProcessor, LocationSpec } from '@backstage/plugin-catalog-node'; const GITHUB_ACTIONS_ANNOTATION = 'github.com/project-slug'; const GITLAB_ACTIONS_ANNOTATION = 'gitlab.com/project-slug'; diff --git a/plugins/catalog-backend/src/modules/core/BuiltinKindsEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/BuiltinKindsEntityProcessor.ts index 4e8cf3fef7..6e05daf591 100644 --- a/plugins/catalog-backend/src/modules/core/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/BuiltinKindsEntityProcessor.ts @@ -53,7 +53,7 @@ import { CatalogProcessorEmit, LocationSpec, processingResult, -} from '../../api'; +} from '@backstage/plugin-catalog-node'; /** @public */ export class BuiltinKindsEntityProcessor implements CatalogProcessor { diff --git a/plugins/catalog-backend/src/modules/core/ConfigLocationEntityProvider.test.ts b/plugins/catalog-backend/src/modules/core/ConfigLocationEntityProvider.test.ts index d61342e3d0..d40c32dfc9 100644 --- a/plugins/catalog-backend/src/modules/core/ConfigLocationEntityProvider.test.ts +++ b/plugins/catalog-backend/src/modules/core/ConfigLocationEntityProvider.test.ts @@ -17,7 +17,7 @@ import { ConfigReader } from '@backstage/config'; import path from 'path'; import { ConfigLocationEntityProvider } from './ConfigLocationEntityProvider'; -import { EntityProviderConnection } from '../../api'; +import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; describe('ConfigLocationEntityProvider', () => { it('should apply mutation with the correct paths in the config', async () => { diff --git a/plugins/catalog-backend/src/modules/core/ConfigLocationEntityProvider.ts b/plugins/catalog-backend/src/modules/core/ConfigLocationEntityProvider.ts index 69827b1380..38f02195f3 100644 --- a/plugins/catalog-backend/src/modules/core/ConfigLocationEntityProvider.ts +++ b/plugins/catalog-backend/src/modules/core/ConfigLocationEntityProvider.ts @@ -17,7 +17,10 @@ import { Config } from '@backstage/config'; import path from 'path'; import { getEntityLocationRef } from '../../processing/util'; -import { EntityProvider, EntityProviderConnection } from '../../api'; +import { + EntityProvider, + EntityProviderConnection, +} from '@backstage/plugin-catalog-node'; import { locationSpecToLocationEntity } from '../../util/conversion'; export class ConfigLocationEntityProvider implements EntityProvider { diff --git a/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts b/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts index 33ab046104..cf5db78203 100644 --- a/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts +++ b/plugins/catalog-backend/src/modules/core/DefaultLocationStore.ts @@ -20,7 +20,10 @@ import { Knex } from 'knex'; import { v4 as uuid } from 'uuid'; import { DbLocationsRow } from '../../database/tables'; import { getEntityLocationRef } from '../../processing/util'; -import { EntityProvider, EntityProviderConnection } from '../../api'; +import { + EntityProvider, + EntityProviderConnection, +} from '@backstage/plugin-catalog-node'; import { locationSpecToLocationEntity } from '../../util/conversion'; import { LocationInput, LocationStore } from '../../service/types'; diff --git a/plugins/catalog-backend/src/modules/core/FileReaderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/FileReaderProcessor.test.ts index 0c114f49a1..3e92bf2b35 100644 --- a/plugins/catalog-backend/src/modules/core/FileReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/FileReaderProcessor.test.ts @@ -19,7 +19,7 @@ import { CatalogProcessorEntityResult, CatalogProcessorErrorResult, CatalogProcessorResult, -} from '../../api'; +} from '@backstage/plugin-catalog-node'; import path from 'path'; import { defaultEntityDataParser } from '../util/parse'; diff --git a/plugins/catalog-backend/src/modules/core/FileReaderProcessor.ts b/plugins/catalog-backend/src/modules/core/FileReaderProcessor.ts index 8ececed59f..af6fba52c0 100644 --- a/plugins/catalog-backend/src/modules/core/FileReaderProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/FileReaderProcessor.ts @@ -24,7 +24,7 @@ import { CatalogProcessorParser, LocationSpec, processingResult, -} from '../../api'; +} from '@backstage/plugin-catalog-node'; const glob = promisify(g); diff --git a/plugins/catalog-backend/src/modules/core/LocationEntityProcessor.test.ts b/plugins/catalog-backend/src/modules/core/LocationEntityProcessor.test.ts index 92ba1fcebf..8f3de7e29b 100644 --- a/plugins/catalog-backend/src/modules/core/LocationEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/LocationEntityProcessor.test.ts @@ -21,7 +21,7 @@ import { } from '@backstage/integration'; import path from 'path'; import { toAbsoluteUrl } from './LocationEntityProcessor'; -import { LocationSpec } from '../../api'; +import { LocationSpec } from '@backstage/plugin-catalog-node'; describe('LocationEntityProcessor', () => { describe('toAbsoluteUrl', () => { diff --git a/plugins/catalog-backend/src/modules/core/LocationEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/LocationEntityProcessor.ts index c168f54bf1..3676b4f727 100644 --- a/plugins/catalog-backend/src/modules/core/LocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/LocationEntityProcessor.ts @@ -22,7 +22,7 @@ import { CatalogProcessor, CatalogProcessorEmit, LocationSpec, -} from '../../api'; +} from '@backstage/plugin-catalog-node'; export function toAbsoluteUrl( integrations: ScmIntegrationRegistry, diff --git a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts index 9bfa9027d9..735536ae35 100644 --- a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts @@ -17,7 +17,7 @@ import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; -import { CatalogProcessorResult } from '../../api'; +import { CatalogProcessorResult } from '@backstage/plugin-catalog-node'; import { jsonPlaceholderResolver, PlaceholderProcessor, @@ -414,7 +414,7 @@ describe('yamlPlaceholderResolver', () => { it('rejects invalid yaml', async () => { read.mockResolvedValue(Buffer.from('a: 1\n----\n', 'utf-8')); await expect(yamlPlaceholderResolver(params)).rejects.toThrow( - 'Placeholder $a found an error in the data at ./file.yaml, YAMLSemanticError: Implicit map keys need to be followed by map values', + /Placeholder \$a found an error in the data at .\/file.yaml, YAMLParseError: Implicit map keys need to be followed by map values at line 2, column 1:\s+a: 1/, ); }); diff --git a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts index be2e9f9f5b..39c9dc59ec 100644 --- a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts @@ -24,7 +24,7 @@ import { CatalogProcessorEmit, LocationSpec, processingResult, -} from '../../api'; +} from '@backstage/plugin-catalog-node'; /** @public */ export type PlaceholderResolverRead = (url: string) => Promise; diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts index fb3f48c939..ee68b2aa39 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts @@ -28,7 +28,7 @@ import { CatalogProcessorEntityResult, CatalogProcessorErrorResult, CatalogProcessorResult, -} from '../../api'; +} from '@backstage/plugin-catalog-node'; import { defaultEntityDataParser } from '../util/parse'; import { UrlReaderProcessor } from './UrlReaderProcessor'; diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts index 7b62690341..0ecd6d5a42 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts @@ -29,7 +29,7 @@ import { CatalogProcessorResult, LocationSpec, processingResult, -} from '../../api'; +} from '@backstage/plugin-catalog-node'; const CACHE_KEY = 'v1'; diff --git a/plugins/catalog-backend/src/modules/util/parse.test.ts b/plugins/catalog-backend/src/modules/util/parse.test.ts index e87de1c9a3..a198a6f036 100644 --- a/plugins/catalog-backend/src/modules/util/parse.test.ts +++ b/plugins/catalog-backend/src/modules/util/parse.test.ts @@ -15,7 +15,7 @@ */ import { parseEntityYaml } from './parse'; -import { processingResult } from '../../api'; +import { processingResult } from '@backstage/plugin-catalog-node'; const testLoc = { target: 'my-loc-target', @@ -156,12 +156,16 @@ describe('parseEntityYaml', () => { ); // Parse errors are always per document - expect(results).toEqual([ - processingResult.generalError( - testLoc, - 'YAML error at my-loc-type:my-loc-target, YAMLSemanticError: Plain value cannot start with reserved character `', - ), - ]); + expect(results.length).toBe(1); + expect(results[0]).toEqual({ + type: 'error', + location: testLoc, + error: expect.objectContaining({ + message: expect.stringMatching( + /YAML error at my-loc-type:my-loc-target, YAMLParseError: Plain value cannot start with reserved character ` at line 1, column 1:/, + ), + }), + }); }); it('should emit parsing errors for individual documents', () => { @@ -185,7 +189,8 @@ describe('parseEntityYaml', () => { ), ); - expect(results).toEqual([ + expect(results.length).toBe(2); + expect(results[0]).toEqual( processingResult.entity(testLoc, { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', @@ -196,11 +201,16 @@ describe('parseEntityYaml', () => { type: 'website', }, }), - processingResult.generalError( - testLoc, - 'YAML error at my-loc-type:my-loc-target, YAMLSemanticError: Nested mappings are not allowed in compact mappings', - ), - ]); + ); + expect(results[1]).toEqual({ + type: 'error', + location: testLoc, + error: expect.objectContaining({ + message: expect.stringMatching( + /YAML error at my-loc-type:my-loc-target, YAMLParseError: Nested mappings are not allowed in compact mappings at line 9, column 19:\s+apiVersion: backstage.io\/v1alpha1/, + ), + }), + }); }); it('must be an object at root', () => { diff --git a/plugins/catalog-backend/src/modules/util/parse.ts b/plugins/catalog-backend/src/modules/util/parse.ts index 65fad5986f..d3aa915e49 100644 --- a/plugins/catalog-backend/src/modules/util/parse.ts +++ b/plugins/catalog-backend/src/modules/util/parse.ts @@ -22,7 +22,7 @@ import { CatalogProcessorResult, LocationSpec, processingResult, -} from '../../api'; +} from '@backstage/plugin-catalog-node'; /** @public */ export function* parseEntityYaml( diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts index 82062a8e2c..fb9518c783 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { stringifyEntityRef } from '@backstage/catalog-model'; -import { assertError, serializeError } from '@backstage/errors'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { assertError, serializeError, stringifyError } from '@backstage/errors'; import { Hash } from 'crypto'; import stableStringify from 'fast-json-stable-stringify'; import { Logger } from 'winston'; @@ -25,7 +25,7 @@ import { CatalogProcessingEngine, CatalogProcessingOrchestrator, EntityProcessingResult, -} from '../processing/types'; +} from './types'; import { Stitcher } from '../stitching/Stitcher'; import { startTaskPipeline } from './TaskPipeline'; @@ -42,6 +42,10 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { private readonly stitcher: Stitcher, private readonly createHash: () => Hash, private readonly pollingIntervalMs: number = 1000, + private readonly onProcessingError?: (event: { + unprocessedEntity: Entity; + errors: Error[]; + }) => Promise | void, ) {} async start() { @@ -122,6 +126,7 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { ); let hashBuilder = this.createHash().update(errorsString); + if (result.ok) { const { entityRefs: parents } = await this.processingDatabase.transaction(tx => @@ -155,6 +160,22 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { // 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, diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts index b2d9846391..30c668bb38 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts @@ -30,7 +30,7 @@ import { CatalogProcessorParser, LocationSpec, processingResult, -} from '../api'; +} from '@backstage/plugin-catalog-node'; import { CatalogRulesEnforcer } from '../ingestion/CatalogRules'; import { DefaultCatalogProcessingOrchestrator } from './DefaultCatalogProcessingOrchestrator'; import { defaultEntityDataParser } from '../modules/util/parse'; diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts index 156503da8e..9effaeb3d9 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts @@ -37,7 +37,7 @@ import { CatalogProcessorParser, LocationSpec, processingResult, -} from '../api'; +} from '@backstage/plugin-catalog-node'; import { CatalogProcessingOrchestrator, EntityProcessingRequest, diff --git a/plugins/catalog-backend/src/processing/ProcessorCacheManager.test.ts b/plugins/catalog-backend/src/processing/ProcessorCacheManager.test.ts index 602557a224..a15ca3c364 100644 --- a/plugins/catalog-backend/src/processing/ProcessorCacheManager.test.ts +++ b/plugins/catalog-backend/src/processing/ProcessorCacheManager.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { CatalogProcessor } from '../api'; +import { CatalogProcessor } from '@backstage/plugin-catalog-node'; import { ProcessorCacheManager } from './ProcessorCacheManager'; class MyProcessor implements CatalogProcessor { diff --git a/plugins/catalog-backend/src/processing/ProcessorCacheManager.ts b/plugins/catalog-backend/src/processing/ProcessorCacheManager.ts index e54641f572..8e0e382910 100644 --- a/plugins/catalog-backend/src/processing/ProcessorCacheManager.ts +++ b/plugins/catalog-backend/src/processing/ProcessorCacheManager.ts @@ -15,7 +15,10 @@ */ import { JsonObject, JsonValue } from '@backstage/types'; -import { CatalogProcessor, CatalogProcessorCache } from '../api'; +import { + CatalogProcessor, + CatalogProcessorCache, +} from '@backstage/plugin-catalog-node'; import { isObject } from './util'; class SingleProcessorSubCache implements CatalogProcessorCache { diff --git a/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts b/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts index 033dfd4ae3..19f3b33df4 100644 --- a/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts +++ b/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts @@ -22,14 +22,18 @@ import { } from '@backstage/catalog-model'; import { assertError } from '@backstage/errors'; import { Logger } from 'winston'; -import { CatalogProcessorResult, EntityRelationSpec } from '../api'; +import { + CatalogProcessorResult, + DeferredEntity, + EntityRelationSpec, +} from '@backstage/plugin-catalog-node'; import { locationSpecToLocationEntity } from '../util/conversion'; -import { DeferredEntity, RefreshKeyData } from './types'; import { getEntityLocationRef, getEntityOriginLocationRef, validateEntityEnvelope, } from './util'; +import { RefreshKeyData } from './types'; /** * Helper class for aggregating all of the emitted data from processors. diff --git a/plugins/catalog-backend/src/processing/connectEntityProviders.ts b/plugins/catalog-backend/src/processing/connectEntityProviders.ts index d7015ea52e..70a43de589 100644 --- a/plugins/catalog-backend/src/processing/connectEntityProviders.ts +++ b/plugins/catalog-backend/src/processing/connectEntityProviders.ts @@ -23,7 +23,7 @@ import { EntityProvider, EntityProviderConnection, EntityProviderMutation, -} from '../api'; +} from '@backstage/plugin-catalog-node'; class Connection implements EntityProviderConnection { readonly validateEntityEnvelope = entityEnvelopeSchemaValidator(); diff --git a/plugins/catalog-backend/src/processing/index.ts b/plugins/catalog-backend/src/processing/index.ts index b392c3aa76..5ed8831779 100644 --- a/plugins/catalog-backend/src/processing/index.ts +++ b/plugins/catalog-backend/src/processing/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -export type { CatalogProcessingEngine, DeferredEntity } from './types'; +export type { CatalogProcessingEngine } from './types'; export { createRandomProcessingInterval } from './refresh'; export type { ProcessingIntervalFunction } from './refresh'; diff --git a/plugins/catalog-backend/src/processing/types.ts b/plugins/catalog-backend/src/processing/types.ts index e94125b6f5..ee5e5c48f2 100644 --- a/plugins/catalog-backend/src/processing/types.ts +++ b/plugins/catalog-backend/src/processing/types.ts @@ -16,7 +16,10 @@ import { Entity } from '@backstage/catalog-model'; import { JsonObject } from '@backstage/types'; -import { EntityRelationSpec } from '../api'; +import { + DeferredEntity, + EntityRelationSpec, +} from '@backstage/plugin-catalog-node'; /** * The request to process an entity. @@ -28,7 +31,7 @@ export type EntityProcessingRequest = { }; /** * The result of processing an entity. - * @public + * @internal */ export type EntityProcessingResult = | { @@ -47,7 +50,6 @@ export type EntityProcessingResult = /** * A string to associate to the entity itself. - * @public */ export type RefreshKeyData = { key: string; @@ -61,15 +63,6 @@ export interface CatalogProcessingOrchestrator { process(request: EntityProcessingRequest): Promise; } -/** - * Entities that are not yet processed. - * @public - */ -export type DeferredEntity = { - entity: Entity; - locationKey?: string; -}; - /** @public */ export interface CatalogProcessingEngine { start(): Promise; diff --git a/plugins/catalog-backend/src/processing/util.ts b/plugins/catalog-backend/src/processing/util.ts index b1948cf559..bd96763a54 100644 --- a/plugins/catalog-backend/src/processing/util.ts +++ b/plugins/catalog-backend/src/processing/util.ts @@ -27,7 +27,7 @@ import { JsonObject, JsonValue } from '@backstage/types'; import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import path from 'path'; -import { LocationSpec } from '../api'; +import { LocationSpec } from '@backstage/plugin-catalog-node'; export function isLocationEntity(entity: Entity): entity is LocationEntity { return entity.kind === 'Location'; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 0d4ddc8e71..a9ba5bbb23 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -17,6 +17,7 @@ import { PluginDatabaseManager, UrlReader } from '@backstage/backend-common'; import { DefaultNamespaceEntityPolicy, + Entity, EntityPolicies, EntityPolicy, FieldFormatEntityPolicy, @@ -36,7 +37,7 @@ import { CatalogProcessor, CatalogProcessorParser, EntityProvider, -} from '../api'; +} from '@backstage/plugin-catalog-node'; import { AnnotateLocationEntityProcessor, BuiltinKindsEntityProcessor, @@ -56,7 +57,7 @@ import { } from '../modules/core/PlaceholderProcessor'; import { defaultEntityDataParser } from '../modules/util/parse'; import { LocationAnalyzer } from '../ingestion/types'; -import { CatalogProcessingEngine } from '../processing/types'; +import { CatalogProcessingEngine } from '../processing'; import { DefaultProcessingDatabase } from '../database/DefaultProcessingDatabase'; import { applyDatabaseMigrations } from '../database/migrations'; import { DefaultCatalogProcessingEngine } from '../processing/DefaultCatalogProcessingEngine'; @@ -133,6 +134,10 @@ export class CatalogBuilder { private processors: CatalogProcessor[]; private processorsReplace: boolean; private parser: CatalogProcessorParser | undefined; + private onProcessingError?: (event: { + unprocessedEntity: Entity; + errors: Error[]; + }) => Promise | void; private processingInterval: ProcessingIntervalFunction = createRandomProcessingInterval({ minSeconds: 100, @@ -447,6 +452,10 @@ export class CatalogBuilder { orchestrator, stitcher, () => createHash('sha1'), + 1000, + event => { + this.onProcessingError?.(event); + }, ); const locationAnalyzer = @@ -478,6 +487,15 @@ export class CatalogBuilder { }; } + subscribe(options: { + onProcessingError: (event: { + unprocessedEntity: Entity; + errors: Error[]; + }) => Promise | void; + }) { + this.onProcessingError = options.onProcessingError; + } + private buildEntityPolicy(): EntityPolicy { const entityPolicies: EntityPolicy[] = this.entityPoliciesReplace ? [new SchemaValidEntityPolicy(), ...this.entityPolicies] diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts new file mode 100644 index 0000000000..b82797ec8b --- /dev/null +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -0,0 +1,104 @@ +/* + * 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 { + configServiceRef, + createBackendPlugin, + databaseServiceRef, + loggerServiceRef, + loggerToWinstonLogger, + permissionsServiceRef, + urlReaderServiceRef, + httpRouterServiceRef, +} from '@backstage/backend-plugin-api'; +import { CatalogBuilder } from './CatalogBuilder'; +import { + CatalogProcessor, + CatalogProcessingExtensionPoint, + catalogProcessingExtentionPoint, + EntityProvider, +} from '@backstage/plugin-catalog-node'; + +class CatalogExtensionPointImpl implements CatalogProcessingExtensionPoint { + #processors = new Array(); + #entityProviders = new Array(); + + addProcessor(processor: CatalogProcessor): void { + this.#processors.push(processor); + } + + addEntityProvider(provider: EntityProvider): void { + this.#entityProviders.push(provider); + } + + get processors() { + return this.#processors; + } + + get entityProviders() { + return this.#entityProviders; + } +} + +/** + * Catalog plugin + * @alpha + */ +export const catalogPlugin = createBackendPlugin({ + id: 'catalog', + register(env) { + const processingExtensions = new CatalogExtensionPointImpl(); + // plugins depending on this API will be initialized before this plugins init method is executed. + env.registerExtensionPoint( + catalogProcessingExtentionPoint, + processingExtensions, + ); + + env.registerInit({ + deps: { + logger: loggerServiceRef, + config: configServiceRef, + reader: urlReaderServiceRef, + permissions: permissionsServiceRef, + database: databaseServiceRef, + httpRouter: httpRouterServiceRef, + }, + async init({ + logger, + config, + reader, + database, + permissions, + httpRouter, + }) { + const winstonLogger = loggerToWinstonLogger(logger); + const builder = await CatalogBuilder.create({ + config, + reader, + permissions, + database, + logger: winstonLogger, + }); + builder.addProcessor(...processingExtensions.processors); + builder.addEntityProvider(...processingExtensions.entityProviders); + const { processingEngine, router } = await builder.build(); + + await processingEngine.start(); + + httpRouter.use(router); + }, + }); + }, +}); diff --git a/plugins/catalog-backend/src/service/DefaultLocationService.ts b/plugins/catalog-backend/src/service/DefaultLocationService.ts index fc6ffb0825..2c051a6e66 100644 --- a/plugins/catalog-backend/src/service/DefaultLocationService.ts +++ b/plugins/catalog-backend/src/service/DefaultLocationService.ts @@ -21,13 +21,11 @@ import { stringifyEntityRef, } from '@backstage/catalog-model'; import { Location } from '@backstage/catalog-client'; -import { - CatalogProcessingOrchestrator, - DeferredEntity, -} from '../processing/types'; +import { CatalogProcessingOrchestrator } from '../processing/types'; import { LocationInput, LocationService, LocationStore } from './types'; import { locationSpecToMetadataName } from '../util/conversion'; import { InputError } from '@backstage/errors'; +import { DeferredEntity } from '@backstage/plugin-catalog-node'; export class DefaultLocationService implements LocationService { constructor( diff --git a/plugins/catalog-backend/src/service/index.ts b/plugins/catalog-backend/src/service/index.ts index b1e91e4382..7e0c6025c3 100644 --- a/plugins/catalog-backend/src/service/index.ts +++ b/plugins/catalog-backend/src/service/index.ts @@ -16,3 +16,4 @@ export type { CatalogEnvironment } from './CatalogBuilder'; export { CatalogBuilder } from './CatalogBuilder'; +export { catalogPlugin } from './CatalogPlugin'; diff --git a/plugins/catalog-backend/src/util/conversion.ts b/plugins/catalog-backend/src/util/conversion.ts index d61304a419..83302722d2 100644 --- a/plugins/catalog-backend/src/util/conversion.ts +++ b/plugins/catalog-backend/src/util/conversion.ts @@ -23,7 +23,7 @@ import { stringifyLocationRef, } from '@backstage/catalog-model'; import { createHash } from 'crypto'; -import { LocationSpec } from '../api'; +import { LocationSpec } from '@backstage/plugin-catalog-node'; export function locationSpecToMetadataName(location: LocationSpec) { const hash = createHash('sha1') diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 042e85f792..49f248ed41 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -53,7 +53,7 @@ "react-hook-form": "^7.12.2", "react-router": "6.0.0-beta.0", "react-use": "^17.2.4", - "yaml": "^1.10.0" + "yaml": "^2.0.0" }, "peerDependencies": { "@types/react": "^16.13.1 || ^17.0.0", diff --git a/plugins/catalog-node/.eslintrc.js b/plugins/catalog-node/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/catalog-node/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/catalog-node/README.md b/plugins/catalog-node/README.md new file mode 100644 index 0000000000..37106bef06 --- /dev/null +++ b/plugins/catalog-node/README.md @@ -0,0 +1,3 @@ +# plugin-catalog-node + +Houses types and utilities for building catalog modules. diff --git a/plugins/catalog-node/api-report.md b/plugins/catalog-node/api-report.md new file mode 100644 index 0000000000..0449708bc3 --- /dev/null +++ b/plugins/catalog-node/api-report.md @@ -0,0 +1,170 @@ +## API Report File for "@backstage/plugin-catalog-node" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { CompoundEntityRef } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; +import { JsonValue } from '@backstage/types'; +import { ServiceRef } from '@backstage/backend-plugin-api'; + +// @alpha (undocumented) +export interface CatalogProcessingExtensionPoint { + // (undocumented) + addEntityProvider(provider: EntityProvider): void; + // (undocumented) + addProcessor(processor: CatalogProcessor): void; +} + +// @alpha (undocumented) +export const catalogProcessingExtentionPoint: ServiceRef; + +// @public (undocumented) +export type CatalogProcessor = { + getProcessorName(): string; + readLocation?( + location: LocationSpec, + optional: boolean, + emit: CatalogProcessorEmit, + parser: CatalogProcessorParser, + cache: CatalogProcessorCache, + ): Promise; + preProcessEntity?( + entity: Entity, + location: LocationSpec, + emit: CatalogProcessorEmit, + originLocation: LocationSpec, + cache: CatalogProcessorCache, + ): Promise; + validateEntityKind?(entity: Entity): Promise; + postProcessEntity?( + entity: Entity, + location: LocationSpec, + emit: CatalogProcessorEmit, + cache: CatalogProcessorCache, + ): Promise; +}; + +// @public +export interface CatalogProcessorCache { + get(key: string): Promise; + set(key: string, value: ItemType): Promise; +} + +// @public (undocumented) +export type CatalogProcessorEmit = (generated: CatalogProcessorResult) => void; + +// @public (undocumented) +export type CatalogProcessorEntityResult = { + type: 'entity'; + entity: Entity; + location: LocationSpec; +}; + +// @public (undocumented) +export type CatalogProcessorErrorResult = { + type: 'error'; + error: Error; + location: LocationSpec; +}; + +// @public (undocumented) +export type CatalogProcessorLocationResult = { + type: 'location'; + location: LocationSpec; +}; + +// @public +export type CatalogProcessorParser = (options: { + data: Buffer; + location: LocationSpec; +}) => AsyncIterable; + +// @public (undocumented) +export type CatalogProcessorRefreshKeysResult = { + type: 'refresh'; + key: string; +}; + +// @public (undocumented) +export type CatalogProcessorRelationResult = { + type: 'relation'; + relation: EntityRelationSpec; +}; + +// @public (undocumented) +export type CatalogProcessorResult = + | CatalogProcessorLocationResult + | CatalogProcessorEntityResult + | CatalogProcessorRelationResult + | CatalogProcessorErrorResult + | CatalogProcessorRefreshKeysResult; + +// @public +export type DeferredEntity = { + entity: Entity; + locationKey?: string; +}; + +// @public +export interface EntityProvider { + connect(connection: EntityProviderConnection): Promise; + getProviderName(): string; +} + +// @public +export interface EntityProviderConnection { + applyMutation(mutation: EntityProviderMutation): Promise; +} + +// @public +export type EntityProviderMutation = + | { + type: 'full'; + entities: DeferredEntity[]; + } + | { + type: 'delta'; + added: DeferredEntity[]; + removed: DeferredEntity[]; + }; + +// @public +export type EntityRelationSpec = { + source: CompoundEntityRef; + type: string; + target: CompoundEntityRef; +}; + +// @public +export type LocationSpec = { + type: string; + target: string; + presence?: 'optional' | 'required'; +}; + +// @public +export const processingResult: Readonly<{ + readonly notFoundError: ( + atLocation: LocationSpec, + message: string, + ) => CatalogProcessorResult; + readonly inputError: ( + atLocation: LocationSpec, + message: string, + ) => CatalogProcessorResult; + readonly generalError: ( + atLocation: LocationSpec, + message: string, + ) => CatalogProcessorResult; + readonly location: (newLocation: LocationSpec) => CatalogProcessorResult; + readonly entity: ( + atLocation: LocationSpec, + newEntity: Entity, + ) => CatalogProcessorResult; + readonly relation: (spec: EntityRelationSpec) => CatalogProcessorResult; + readonly refresh: (key: string) => CatalogProcessorResult; +}>; +``` diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json new file mode 100644 index 0000000000..d6228c717f --- /dev/null +++ b/plugins/catalog-node/package.json @@ -0,0 +1,41 @@ +{ + "name": "@backstage/plugin-catalog-node", + "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts", + "alphaTypes": "dist/index.alpha.d.ts" + }, + "backstage": { + "role": "node-library" + }, + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build --experimental-type-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" + }, + "dependencies": { + "@backstage/backend-plugin-api": "^0.0.0", + "@backstage/catalog-model": "^1.1.0-next.2", + "@backstage/errors": "1.1.0-next.0", + "@backstage/types": "^1.0.0" + }, + "devDependencies": { + "@backstage/backend-common": "^0.14.1-next.2", + "@backstage/cli": "^0.18.0-next.2" + }, + "files": [ + "dist", + "alpha" + ] +} diff --git a/plugins/catalog-backend/src/api/common.ts b/plugins/catalog-node/src/api/common.ts similarity index 100% rename from plugins/catalog-backend/src/api/common.ts rename to plugins/catalog-node/src/api/common.ts diff --git a/plugins/catalog-backend/src/api/index.ts b/plugins/catalog-node/src/api/index.ts similarity index 100% rename from plugins/catalog-backend/src/api/index.ts rename to plugins/catalog-node/src/api/index.ts diff --git a/plugins/catalog-backend/src/api/processingResult.ts b/plugins/catalog-node/src/api/processingResult.ts similarity index 100% rename from plugins/catalog-backend/src/api/processingResult.ts rename to plugins/catalog-node/src/api/processingResult.ts diff --git a/plugins/catalog-backend/src/api/processor.ts b/plugins/catalog-node/src/api/processor.ts similarity index 100% rename from plugins/catalog-backend/src/api/processor.ts rename to plugins/catalog-node/src/api/processor.ts diff --git a/plugins/catalog-backend/src/api/provider.ts b/plugins/catalog-node/src/api/provider.ts similarity index 100% rename from plugins/catalog-backend/src/api/provider.ts rename to plugins/catalog-node/src/api/provider.ts diff --git a/plugins/catalog-node/src/extensions.ts b/plugins/catalog-node/src/extensions.ts new file mode 100644 index 0000000000..560d9d4db1 --- /dev/null +++ b/plugins/catalog-node/src/extensions.ts @@ -0,0 +1,34 @@ +/* + * 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 { createServiceRef } from '@backstage/backend-plugin-api'; +import { EntityProvider } from './api'; +import { CatalogProcessor } from './api/processor'; + +/** + * @alpha + */ +export interface CatalogProcessingExtensionPoint { + addProcessor(processor: CatalogProcessor): void; + addEntityProvider(provider: EntityProvider): void; +} + +/** + * @alpha + */ +export const catalogProcessingExtentionPoint = + createServiceRef({ + id: 'catalog.processing', + }); diff --git a/plugins/catalog-node/src/index.ts b/plugins/catalog-node/src/index.ts new file mode 100644 index 0000000000..fc7a1b1b67 --- /dev/null +++ b/plugins/catalog-node/src/index.ts @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/** + * The catalog-backend-node module for `@backstage/plugin-catalog-backend`. + * + * @packageDocumentation + */ + +export type { CatalogProcessingExtensionPoint } from './extensions'; +export { catalogProcessingExtentionPoint } from './extensions'; +export * from './api'; +export * from './processing'; diff --git a/plugins/catalog-node/src/processing/index.ts b/plugins/catalog-node/src/processing/index.ts new file mode 100644 index 0000000000..90b87aa850 --- /dev/null +++ b/plugins/catalog-node/src/processing/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type { DeferredEntity } from './types'; diff --git a/plugins/catalog-node/src/processing/types.ts b/plugins/catalog-node/src/processing/types.ts new file mode 100644 index 0000000000..25e76faea3 --- /dev/null +++ b/plugins/catalog-node/src/processing/types.ts @@ -0,0 +1,26 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; + +/** + * Entities that are not yet processed. + * @public + */ +export type DeferredEntity = { + entity: Entity; + locationKey?: string; +}; diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 3f3ee5f402..560690245f 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -55,7 +55,7 @@ "qs": "^6.9.4", "react-router": "6.0.0-beta.0", "react-use": "^17.2.4", - "yaml": "^1.10.0", + "yaml": "^2.0.0", "zen-observable": "^0.8.15" }, "peerDependencies": { diff --git a/plugins/gocd/src/components/Select/Select.test.tsx b/plugins/gocd/src/components/Select/Select.test.tsx new file mode 100644 index 0000000000..fb467b946f --- /dev/null +++ b/plugins/gocd/src/components/Select/Select.test.tsx @@ -0,0 +1,75 @@ +/* + * 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 { renderInTestApp } from '@backstage/test-utils'; +import React from 'react'; +import { Item, SelectComponent } from './Select'; +import userEvent from '@testing-library/user-event'; + +describe('Select', () => { + const testLabel = 'Date Select'; + const testItems: Item[] = [ + { label: 'Last month', value: 'lastMonth' }, + { label: 'All', value: 'all' }, + ]; + + const user = userEvent.setup(); + + it('should render', async () => { + const rendered = await renderInTestApp( + undefined} + />, + ); + expect(rendered.getAllByText(testLabel)).toHaveLength(2); + }); + + describe('when the user hasn`t clicked on it', () => { + it('should only render the current select item', async () => { + const rendered = await renderInTestApp( + undefined} + />, + ); + expect(rendered.getByText(testItems[0].label)).toBeInTheDocument(); + expect(rendered.queryByText(testItems[1].label)).toBeNull(); + }); + }); + + describe('when the user clicked on it', () => { + it('should render all the items', async () => { + const rendered = await renderInTestApp( + undefined} + />, + ); + user.tab(); + userEvent.keyboard('{enter}'); + + expect(rendered.getAllByText(testItems[0].label)).toHaveLength(2); + expect(rendered.getByText(testItems[1].label)).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 7eba901c54..e21173910e 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -41,7 +41,7 @@ "morgan": "^1.10.0", "uuid": "^8.0.0", "winston": "^3.2.1", - "yaml": "^1.9.2", + "yaml": "^2.0.0", "yn": "^4.0.0", "yup": "^0.32.9" }, diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index ed97db5617..2ac49898d2 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -40,10 +40,6 @@ ## 1.4.0-next.0 -### Minor Changes - -- 3500c13a33: Added a new `/v2/dry-run` endpoint that allows for a synchronous dry run of a provided template. A `supportsDryRun` option has been added to `createTemplateAction`, which signals whether the action should be executed during dry runs. When enabled, the action context will have the new `isDryRun` property set to signal if the action is being executed during a dry run. - ### Patch Changes - Updated dependencies @@ -86,6 +82,7 @@ - Added a route under `/v2/tasks` to list tasks by a `userEntityRef` using the `createdBy` query parameter - c042c5eaff: Add an option to not protect the default branch. - f93af969cd: Added the ability to support running of templates that are not in the `default` namespace +- 3500c13a33: Added a new `/v2/dry-run` endpoint that allows for a synchronous dry run of a provided template. A `supportsDryRun` option has been added to `createTemplateAction`, which signals whether the action should be executed during dry runs. When enabled, the action context will have the new `isDryRun` property set to signal if the action is being executed during a dry run. ### Patch Changes diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 2053f01e39..dfca5994fd 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -5,6 +5,7 @@ ```ts /// +import { BackendRegistrable } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; @@ -543,6 +544,9 @@ export type RunCommandOptions = { logStream?: Writable; }; +// @alpha +export const scaffolderCatalogModule: (option: unknown) => BackendRegistrable; + // @public (undocumented) export class ScaffolderEntitiesProcessor implements CatalogProcessor { // (undocumented) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index a1bb552435..29adfd0fa9 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -9,7 +9,8 @@ "publishConfig": { "access": "public", "main": "dist/index.cjs.js", - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "alphaTypes": "dist/index.alpha.d.ts" }, "backstage": { "role": "backend-plugin" @@ -25,7 +26,7 @@ ], "scripts": { "start": "backstage-cli package start", - "build": "backstage-cli package build", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", @@ -42,6 +43,8 @@ "@backstage/integration": "^1.2.2-next.2", "@backstage/plugin-catalog-backend": "^1.2.1-next.2", "@backstage/plugin-scaffolder-common": "^1.1.2-next.0", + "@backstage/backend-plugin-api": "^0.0.0", + "@backstage/plugin-catalog-node": "^0.0.0", "@backstage/types": "^1.0.0", "@gitbeaker/core": "^35.6.0", "@gitbeaker/node": "^35.1.0", @@ -65,12 +68,12 @@ "morgan": "^1.10.0", "node-fetch": "^2.6.7", "nunjucks": "^3.2.3", - "octokit": "^1.7.1", + "octokit": "^2.0.0", "octokit-plugin-create-pull-request": "^3.10.0", "p-limit": "^3.1.0", "uuid": "^8.2.0", "winston": "^3.2.1", - "yaml": "^1.10.0", + "yaml": "^2.0.0", "vm2": "^3.9.6", "zen-observable": "^0.8.15", "zod": "^3.11.6" @@ -90,9 +93,10 @@ "mock-fs": "^5.1.0", "msw": "^0.43.0", "supertest": "^6.1.3", - "yaml": "^1.10.0" + "yaml": "^2.0.0" }, "files": [ + "alpha", "dist", "migrations", "config.d.ts", diff --git a/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.test.ts b/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.test.ts new file mode 100644 index 0000000000..f07eb0f7ae --- /dev/null +++ b/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.test.ts @@ -0,0 +1,42 @@ +/* + * 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 { BackendInitRegistry } from '@backstage/backend-plugin-api'; +import { ScaffolderEntitiesProcessor } from '../processor'; +import { scaffolderCatalogModule } from './ScaffolderCatalogModule'; + +describe('ScaffolderCatalogModule', () => { + it('should register the extension point', () => { + // TODO(jhaals): clean this up and add test helpers for backend system. + const ext = scaffolderCatalogModule({}); + expect(ext.id).toBe('catalog.scaffolder.module'); + const registry: jest.Mocked = { + registerInit: jest.fn(), + } as any; + ext.register(registry); + + const extensionPoint = { + addProcessor: jest.fn(), + }; + + registry.registerInit.mock.calls[0][0].init({ + catalogProcessingExtensionPoint: extensionPoint, + }); + expect(extensionPoint.addProcessor).toHaveBeenCalledWith( + new ScaffolderEntitiesProcessor(), + ); + }); +}); diff --git a/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.ts b/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.ts new file mode 100644 index 0000000000..bdce46a31a --- /dev/null +++ b/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.ts @@ -0,0 +1,39 @@ +/* + * 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 { createBackendModule } from '@backstage/backend-plugin-api'; +import { catalogProcessingExtentionPoint } from '@backstage/plugin-catalog-node'; +import { ScaffolderEntitiesProcessor } from '../processor'; + +/** + * @alpha + * Registers the ScaffolderEntitiesProcessor with the catalog processing extension point. + */ +export const scaffolderCatalogModule = createBackendModule({ + moduleId: 'scaffolder.module', + pluginId: 'catalog', + register(env) { + env.registerInit({ + deps: { + catalogProcessingExtensionPoint: catalogProcessingExtentionPoint, + }, + async init({ catalogProcessingExtensionPoint }) { + catalogProcessingExtensionPoint.addProcessor( + new ScaffolderEntitiesProcessor(), + ); + }, + }); + }, +}); diff --git a/plugins/scaffolder-backend/src/extension/index.ts b/plugins/scaffolder-backend/src/extension/index.ts new file mode 100644 index 0000000000..c827d9cafd --- /dev/null +++ b/plugins/scaffolder-backend/src/extension/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { scaffolderCatalogModule } from './ScaffolderCatalogModule'; diff --git a/plugins/scaffolder-backend/src/index.ts b/plugins/scaffolder-backend/src/index.ts index 5978b6389b..a1032449d5 100644 --- a/plugins/scaffolder-backend/src/index.ts +++ b/plugins/scaffolder-backend/src/index.ts @@ -24,3 +24,4 @@ export * from './scaffolder'; export * from './service/router'; export * from './lib'; export * from './processor'; +export * from './extension'; diff --git a/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts b/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts index e1ea676ec5..b24a867504 100644 --- a/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts +++ b/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts @@ -97,6 +97,52 @@ describe('serializeDirectoryContents', () => { ]); }); + it('should ignore symlinked files', async () => { + mockFs({ + root: { + 'a.txt': 'some text', + sym: mockFs.symlink({ + path: './a.txt', + }), + }, + }); + + await expect(serializeDirectoryContents('root')).resolves.toEqual([ + { + path: 'a.txt', + executable: false, + content: Buffer.from('some text', 'utf8'), + }, + ]); + }); + + it('should ignore symlinked folder files', async () => { + mockFs({ + root: { + 'a.txt': 'some text', + linkme: { + 'b.txt': 'lols', + }, + sym: mockFs.symlink({ + path: './linkme', + }), + }, + }); + + await expect(serializeDirectoryContents('root')).resolves.toEqual([ + { + path: 'a.txt', + executable: false, + content: Buffer.from('some text', 'utf8'), + }, + { + path: 'linkme/b.txt', + executable: false, + content: Buffer.from('lols', 'utf8'), + }, + ]); + }); + it('should ignore gitignored files', async () => { mockFs({ root: { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts index 8f641fe2e2..06def076ea 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts @@ -186,6 +186,10 @@ describe('fetch:template', () => { }, '.${{ values.name }}': '${{ values.itemList | dump }}', 'a-binary-file.png': aBinaryFile, + symlink: mockFs.symlink({ + path: 'a-binary-file.png', + }), + '{% if values.showDummyFile %}dummy-file.txt{% else %}{% endif %}': 'dummy file', '${{ "dummy-file2.txt" if values.showDummyFile else "" }}': @@ -265,6 +269,17 @@ describe('fetch:template', () => { .then(fObj => fObj.mode), ).resolves.toEqual(parseInt('100755', 8)); }); + it('copies file symlinks as-is without processing them', async () => { + await expect( + fs + .lstat(`${workspacePath}/target/symlink`) + .then(i => i.isSymbolicLink()), + ).resolves.toBe(true); + + await expect( + fs.realpath(`${workspacePath}/target/symlink`), + ).resolves.toBe(`${workspacePath}/target/a-binary-file.png`); + }); }); describe('copyWithoutRender', () => { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index c6d6b86e7b..a1afabc39e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -229,10 +229,11 @@ export function createFetchTemplateAction(options: { await fs.ensureDir(outputPath); } else { const inputFilePath = resolveSafeChildPath(templateDir, location); + const stats = await fs.promises.lstat(inputFilePath); - if (await isBinaryFile(inputFilePath)) { + if (stats.isSymbolicLink() || (await isBinaryFile(inputFilePath))) { ctx.logger.info( - `Copying binary file ${location} to template output path.`, + `Copying file binary or symbolic link at ${location}, to template output path.`, ); await fs.copy(inputFilePath, outputPath); } else { diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index a8a606c73d..26a08c4b68 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -44,12 +44,6 @@ ## 1.4.0-next.0 -### Minor Changes - -- 3500c13a33: A new template editor has been added which is accessible via the context menu on the top right hand corner of the Create page. It allows you to load a template from a local directory, edit it with a preview, execute it in dry-run mode, and view the results. Note that the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API) must be supported by your browser for this to be available. - - To support the new template editor the `ScaffolderApi` now has an optional `dryRun` method, which is implemented by the default `ScaffolderClient`. - ### Patch Changes - 37539e29d8: The template editor now shows the cause of request errors that happen during a dry-run. @@ -92,6 +86,9 @@ - 72dfcbc8bf: Gerrit Integration: Implemented a `RepoUrlPicker` for Gerrit. - f93af969cd: Added the ability to support running of templates that are not in the `default` namespace +- 3500c13a33: A new template editor has been added which is accessible via the context menu on the top right hand corner of the Create page. It allows you to load a template from a local directory, edit it with a preview, execute it in dry-run mode, and view the results. Note that the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API) must be supported by your browser for this to be available. + + To support the new template editor the `ScaffolderApi` now has an optional `dryRun` method, which is implemented by the default `ScaffolderClient`. ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index e6223d1952..ad46f47433 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -72,7 +72,7 @@ "react-router-dom": "6.0.0-beta.0", "react-use": "^17.2.4", "use-immer": "^0.7.0", - "yaml": "^1.9.2", + "yaml": "^2.0.0", "zen-observable": "^0.8.15" }, "peerDependencies": { diff --git a/scripts/prepare-release.js b/scripts/prepare-release.js index e4c987733d..962d54abb0 100755 --- a/scripts/prepare-release.js +++ b/scripts/prepare-release.js @@ -394,10 +394,10 @@ async function main() { if (isMainBranch) { console.log('Main release, updating package versions'); await updatePackageVersions(repo); + await ensureCreateAppChangeset(); } await updateBackstageReleaseVersion(repo, isMainBranch ? 'minor' : 'patch'); - await ensureCreateAppChangeset(); } main().catch(error => { diff --git a/yarn.lock b/yarn.lock index 79ab273b77..ed3ec4e0eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -359,9 +359,9 @@ uuid "^8.3.0" "@azure/storage-blob@^12.5.0": - version "12.10.0" - resolved "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.10.0.tgz#b92269f45a1765700a900b41ca81a474a6e36ea4" - integrity sha512-FBEPKGnvtQJS8V8Tg1P9obgmVD9AodrIfwtwhBpsjenClhFyugMp3HPJY0tF7rInUB/CivKBCbnQKrUnKxqxzw== + version "12.11.0" + resolved "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.11.0.tgz#2e27902ab293715411ab1f7c8fae422ad0b4b827" + integrity sha512-na+FisoARuaOWaHWpmdtk3FeuTWf2VWamdJ9/TJJzj5ZdXPLC3juoDgFs6XVuJIoK30yuBpyFBEDXVRK4pB7Tg== dependencies: "@azure/abort-controller" "^1.0.0" "@azure/core-http" "^2.0.0" @@ -2041,9 +2041,9 @@ "@lezer/highlight" "^1.0.0" "@codemirror/view@^6.0.0": - version "6.0.2" - resolved "https://registry.npmjs.org/@codemirror/view/-/view-6.0.2.tgz#27f4d08edd10a3678cf15390b4fba5e2a7220873" - integrity sha512-mnVT/q1JvKPjpmjXJNeCi/xHyaJ3abGJsumIVpdQ1nE1MXAyHf7GHWt8QpWMUvDiqF0j+inkhVR2OviTdFFX7Q== + version "6.0.3" + resolved "https://registry.npmjs.org/@codemirror/view/-/view-6.0.3.tgz#c0f6cf5c66d76cbe64227717708a714338ac76a4" + integrity sha512-1gDBymhbx2DZzwnR/rNUu1LiQqjxBJtFiB+4uLR6tHQ6vKhTIwUsP5uZUQ7SM7JxVx3UihMynnTqjcsC+mczZg== dependencies: "@codemirror/state" "^6.0.0" style-mod "^4.0.0" @@ -4871,20 +4871,20 @@ consola "^2.15.0" node-fetch "^2.6.1" -"@octokit/app@^12.0.4": - version "12.0.5" - resolved "https://registry.npmjs.org/@octokit/app/-/app-12.0.5.tgz#0b25446daffcb36967b26944410eab1ccbba0c06" - integrity sha512-lM3pIfx2h+UbvsXHFVs1ApJ9Rmp8LO4ciFSr5q/9MdHmhsH6WtwayieUn875xwB77IoR9r8czxxxASu2WCtdeA== +"@octokit/app@^13.0.0": + version "13.0.3" + resolved "https://registry.npmjs.org/@octokit/app/-/app-13.0.3.tgz#6a438e2ab555a27f09f48e4887e650bdce0347eb" + integrity sha512-eJA7jtKtTTYTfVxf/gBmxLpYjAud4FXQdMMY89AuMcyFboHS9XsI/zUJ0xXdMW0G4yNKPxqAt+KWsUJivtrXpA== dependencies: - "@octokit/auth-app" "^3.3.0" - "@octokit/auth-unauthenticated" "^2.0.4" - "@octokit/core" "^3.4.0" + "@octokit/auth-app" "^4.0.0" + "@octokit/auth-unauthenticated" "^3.0.0" + "@octokit/core" "^4.0.0" "@octokit/oauth-app" "^3.3.2" "@octokit/plugin-paginate-rest" "^2.13.3" "@octokit/types" "^6.27.1" - "@octokit/webhooks" "^9.0.1" + "@octokit/webhooks" "^10.0.0" -"@octokit/auth-app@^3.3.0", "@octokit/auth-app@^3.4.0": +"@octokit/auth-app@^3.4.0": version "3.6.1" resolved "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-3.6.1.tgz#aa5b02cc211175cbc28ce6c03c73373c1206d632" integrity sha512-6oa6CFphIYI7NxxHrdVOzhG7hkcKyGyYocg7lNDSJVauVOLtylg8hNJzoUyPAYKKK0yUeoZamE/lMs2tG+S+JA== @@ -4982,7 +4982,14 @@ dependencies: "@octokit/types" "^6.0.0" -"@octokit/auth-unauthenticated@^2.0.0", "@octokit/auth-unauthenticated@^2.0.4": +"@octokit/auth-token@^3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.0.tgz#6f22c5fc56445c496628488ba6810131558fa4a9" + integrity sha512-MDNFUBcJIptB9At7HiV7VCvU3NcL4GnfCQaP8C5lrxWrRPMJBnemYtehaKSOlaM7AYxeRyj9etenu8LVpSpVaQ== + dependencies: + "@octokit/types" "^6.0.3" + +"@octokit/auth-unauthenticated@^2.0.0": version "2.1.0" resolved "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-2.1.0.tgz#ef97de366836e09f130de4e2205be955f9cf131c" integrity sha512-+baofLfSL0CAv3CfGQ9rxiZZQEX8VNJMGuuS4PgrMRBUL52Ho5+hQYb63UJQshw7EXYMPDZxbXznc0y33cbPqw== @@ -4990,6 +4997,14 @@ "@octokit/request-error" "^2.1.0" "@octokit/types" "^6.0.3" +"@octokit/auth-unauthenticated@^3.0.0": + version "3.0.0" + resolved "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-3.0.0.tgz#6ef7039c11c7e0dd46dd1cabff8aa4bff4f976a1" + integrity sha512-d5qrT3f4HJDCg2uVmmTYfuP3WnBVf6OvWAZkV3NmLANqg3/yae7yUxS8XHpOwZdTh1CNVuF9riJ/UugyjDKKhA== + dependencies: + "@octokit/request-error" "^2.1.0" + "@octokit/types" "^6.0.3" + "@octokit/core@^3.2.3": version "3.2.4" resolved "https://registry.npmjs.org/@octokit/core/-/core-3.2.4.tgz#5791256057a962eca972e31818f02454897fd106" @@ -5002,7 +5017,7 @@ before-after-hook "^2.1.0" universal-user-agent "^6.0.0" -"@octokit/core@^3.3.2", "@octokit/core@^3.4.0", "@octokit/core@^3.5.1": +"@octokit/core@^3.3.2", "@octokit/core@^3.5.1": version "3.5.1" resolved "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz#8601ceeb1ec0e1b1b8217b960a413ed8e947809b" integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw== @@ -5015,6 +5030,19 @@ before-after-hook "^2.2.0" universal-user-agent "^6.0.0" +"@octokit/core@^4.0.0": + version "4.0.2" + resolved "https://registry.npmjs.org/@octokit/core/-/core-4.0.2.tgz#4eaf9c5fd39913b541c5e31a2b8fdc3cf50480bc" + integrity sha512-vgVtE02EF9kXFsjmFoKFCwH1wDspPfDgopRbAlavkGuBJPWF+u5n0xgwP4obmdKNvLM+bB7MI7W31c2E13zgDQ== + dependencies: + "@octokit/auth-token" "^3.0.0" + "@octokit/graphql" "^4.5.8" + "@octokit/request" "^6.0.0" + "@octokit/request-error" "^2.0.5" + "@octokit/types" "^6.0.3" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + "@octokit/endpoint@^6.0.1": version "6.0.3" resolved "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.3.tgz#dd09b599662d7e1b66374a177ab620d8cdf73487" @@ -5172,10 +5200,10 @@ "@octokit/types" "^6.0.3" bottleneck "^2.15.3" -"@octokit/plugin-throttling@^3.5.1": - version "3.5.2" - resolved "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-3.5.2.tgz#8b1797a5f14edbca0b8af619394056ed0ed5c9b5" - integrity sha512-Eu7kfJxU8vmHqWGNszWpg+GVp2tnAfax3XQV5CkYPEE69C+KvInJXW9WajgSeW+cxYe0UVdouzCtcreGNuJo7A== +"@octokit/plugin-throttling@^4.0.0": + version "4.0.1" + resolved "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-4.0.1.tgz#06cd4af959a98fe601d780d3476063f249b5eec1" + integrity sha512-CWAM7+3XkkmzwAd4FrI4+ogOd5oAmMJER8/6AYDTb4ErGuvSdSSGeI5jFw6quNj7qNV7TvSWLTnNgRjSdBEE2A== dependencies: "@octokit/types" "^6.0.1" bottleneck "^2.15.3" @@ -5261,21 +5289,11 @@ dependencies: "@octokit/openapi-types" "^12.4.0" -"@octokit/webhooks-methods@^2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-2.0.0.tgz#1108b9ea661ca6c81e4a8bfa63a09eb27d5bc2db" - integrity sha512-35cfQ4YWlnZnmZKmIxlGPUPLtbkF8lr/A/1Sk1eC0ddLMwQN06dOuLc+dI3YLQS+T+MoNt3DIQ0NynwgKPilig== - "@octokit/webhooks-methods@^3.0.0": version "3.0.0" resolved "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-3.0.0.tgz#4f4443605233f46abc5f85a857ba105095aa1181" integrity sha512-FAIyAchH9JUKXugKMC17ERAXM/56vVJekwXOON46pmUDYfU7uXB4cFY8yc8nYr5ABqVI7KjRKfFt3mZF7OcyUA== -"@octokit/webhooks-types@5.8.0": - version "5.8.0" - resolved "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-5.8.0.tgz#b76d1a3e3ad82cec5680d3c6c3443a620047a6ef" - integrity sha512-8adktjIb76A7viIdayQSFuBEwOzwhDC+9yxZpKNHjfzrlostHCw0/N7JWpWMObfElwvJMk2fY2l1noENCk9wmw== - "@octokit/webhooks-types@6.2.2": version "6.2.2" resolved "https://registry.npmjs.org/@octokit/webhooks-types/-/webhooks-types-6.2.2.tgz#adbec417f2061c3148d757bf39f8831af6d45b68" @@ -5291,16 +5309,6 @@ "@octokit/webhooks-types" "6.2.2" aggregate-error "^3.1.0" -"@octokit/webhooks@^9.0.1": - version "9.26.0" - resolved "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-9.26.0.tgz#cf453bb313da3b66f1a90c84464d978e1c625cce" - integrity sha512-foZlsgrTDwAmD5j2Czn6ji10lbWjGDVsUxTIydjG9KTkAWKJrFapXJgO5SbGxRwfPd3OJdhK3nA2YPqVhxLXqA== - dependencies: - "@octokit/request-error" "^2.0.2" - "@octokit/webhooks-methods" "^2.0.0" - "@octokit/webhooks-types" "5.8.0" - aggregate-error "^3.1.0" - "@open-draft/until@^1.0.3": version "1.0.3" resolved "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" @@ -8560,9 +8568,9 @@ axobject-query@^2.2.0: integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== azure-devops-node-api@^11.0.1: - version "11.1.1" - resolved "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.1.1.tgz#dd1356031fa4e334e016732594e8fee0f68268c4" - integrity sha512-XDG91XzLZ15reP12s3jFkKS8oiagSICjnLwxEYieme4+4h3ZveFOFRA4iYIG40RyHXsiI0mefFYYMFIJbMpWcg== + version "11.2.0" + resolved "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.2.0.tgz#bf04edbef60313117a0507415eed4790a420ad6b" + integrity sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA== dependencies: tunnel "0.0.6" typed-rest-client "^1.8.4" @@ -19764,18 +19772,18 @@ octokit-plugin-create-pull-request@^3.10.0: dependencies: "@octokit/types" "^6.8.2" -octokit@^1.7.1: - version "1.8.1" - resolved "https://registry.npmjs.org/octokit/-/octokit-1.8.1.tgz#399b0032e89e058084a1a1922e40a02e87d4cd61" - integrity sha512-xBLKFIivbl7wnLwxzLYuDO/JDNYxdyxoSjFrl/QMrY/fwGGQYYklvKUDTUyGMU0aXPrQtJ0IZnG3BXpCkDQzWg== +octokit@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/octokit/-/octokit-2.0.2.tgz#46a8d4da3fd12933fc495971470e191af4d9a54c" + integrity sha512-YQ3ysVawtMVp1Tjf0uITLtUW7kf+SGhelgT0xuk25frUNf7sotGxO5wm7XvnEvNL4nnlp1mF2UxqG05dto2iyg== dependencies: - "@octokit/app" "^12.0.4" + "@octokit/app" "^13.0.0" "@octokit/core" "^3.5.1" "@octokit/oauth-app" "^3.5.1" "@octokit/plugin-paginate-rest" "^2.18.0" "@octokit/plugin-rest-endpoint-methods" "^5.14.0" "@octokit/plugin-retry" "^3.0.9" - "@octokit/plugin-throttling" "^3.5.1" + "@octokit/plugin-throttling" "^4.0.0" "@octokit/types" "^6.35.0" oidc-token-hash@^5.0.1: @@ -21551,9 +21559,9 @@ punycode@^2.1.0, punycode@^2.1.1: integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== puppeteer@^15.0.0: - version "15.3.1" - resolved "https://registry.npmjs.org/puppeteer/-/puppeteer-15.3.1.tgz#0ff9b433a8fc3798f5ec82ea4b31ec47857219cf" - integrity sha512-Z+SpYBiS1zUzMXV7Wnhe2pyuVCFAFRTq1UrUWHB2CkLos5v7bXvXYuZ3Fn5pSN5IObxijyx4opNYKTCRnGni6Q== + version "15.3.2" + resolved "https://registry.npmjs.org/puppeteer/-/puppeteer-15.3.2.tgz#62162739044d570ab9907f85b1e1bbcf52adc79e" + integrity sha512-6z4fTHCHTpG3Yu7zqP0mLfCmkNkgw5KSUfLAwuBabz9Pkqoe0Z08hqUx5GNxhhMgEo4YVOSPBshePA6zliznWQ== dependencies: cross-fetch "3.1.5" debug "4.3.4" @@ -23003,9 +23011,9 @@ rollup@^0.63.4: "@types/node" "*" rollup@^2.60.2: - version "2.75.7" - resolved "https://registry.npmjs.org/rollup/-/rollup-2.75.7.tgz#221ff11887ae271e37dcc649ba32ce1590aaa0b9" - integrity sha512-VSE1iy0eaAYNCxEXaleThdFXqZJ42qDBatAwrfnPlENEZ8erQ+0LYX4JXOLPceWfZpV1VtZwZ3dFCuOZiSyFtQ== + version "2.76.0" + resolved "https://registry.npmjs.org/rollup/-/rollup-2.76.0.tgz#c69fe03db530ac53fcb9523b3caa0d3c0b9491a1" + integrity sha512-9jwRIEY1jOzKLj3nsY/yot41r19ITdQrhs+q3ggNWhr9TQgduHqANvPpS32RNpzGklJu3G1AJfvlZLi/6wFgWA== optionalDependencies: fsevents "~2.3.2" @@ -26565,12 +26573,12 @@ yaml-ast-parser@0.0.43, yaml-ast-parser@^0.0.43: resolved "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz#e8a23e6fb4c38076ab92995c5dca33f3d3d7c9bb" integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A== -yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2, yaml@^1.9.2: +yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: version "1.10.2" resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yaml@^2.1.1: +yaml@^2.0.0, yaml@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz#1e06fb4ca46e60d9da07e4f786ea370ed3c3cfec" integrity sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==