Merge remote-tracking branch 'origin/master' into reconfigure

This commit is contained in:
bnechyporenko
2022-07-08 16:44:53 +02:00
149 changed files with 3034 additions and 310 deletions
+6
View File
@@ -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.
-5
View File
@@ -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.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-ldap': patch
---
Fix mapping between users and groups for FreeIPA when using the LdapOrgProcessor
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-node': minor
---
Added alpha exports for the new experimental backend system.
+5
View File
@@ -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.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Don't resolve symlinks, treat them as binary files and copy them as-is
-7
View File
@@ -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`.
+5
View File
@@ -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.
-2
View File
@@ -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",
+14
View File
@@ -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`.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-adr': patch
'@backstage/plugin-scaffolder-backend': patch
---
Updated dependency `octokit` to `^2.0.0`.
+14
View File
@@ -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> | 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.
+5
View File
@@ -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.
+7
View File
@@ -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.
+5
View File
@@ -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`.
+1 -1
View File
@@ -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'
@@ -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.
+4
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+19
View File
@@ -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)
+25
View File
@@ -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<void>;
}
// @public (undocumented)
export function createBackend(options?: CreateBackendOptions): Backend;
// @public (undocumented)
export interface CreateBackendOptions {
// (undocumented)
apis: AnyServiceFactory[];
}
```
+52
View File
@@ -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"
]
}
+23
View File
@@ -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';
@@ -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);
};
},
});
@@ -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;
};
},
});
@@ -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);
};
},
});
@@ -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;
};
},
});
@@ -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);
},
};
};
},
});
@@ -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,
];
@@ -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 });
};
},
});
@@ -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;
};
},
});
@@ -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);
};
},
});
@@ -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),
});
};
},
});
@@ -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),
});
};
},
});
@@ -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<unknown> | ServiceRef<unknown>;
export class BackendInitializer {
#started = false;
#extensions = new Map<BackendRegistrable, unknown>();
#registerInits = new Array<BackendRegisterInit>();
#extensionPoints = new Map<ServiceOrExtensionPoint, unknown>();
#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<unknown>)!(
pluginId,
)),
]),
),
);
}
add<TOptions>(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<void> {
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<ServiceRef<unknown>>();
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<BackendRegisterInit>) {
let registerInitsToOrder = registerInits.slice();
const orderedRegisterInits = new Array<BackendRegisterInit>();
// TODO: Validate duplicates
while (registerInitsToOrder.length > 0) {
const toRemove = new Set<unknown>();
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;
}
}
@@ -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<void> {
await this.#initializer.start();
}
// async stop(): Promise<void> {
// await this.#initializer.stop();
// }
}
@@ -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<string, Map<string, unknown>>;
readonly #factories: Map<string, AnyServiceFactory>;
constructor(factories: AnyServiceFactory[]) {
this.#factories = new Map(factories.map(f => [f.service.id, f]));
this.#implementations = new Map();
}
get<T>(ref: ServiceRef<T>): FactoryFunc<T> | undefined {
const factory = this.#factories.get(ref.id);
if (!factory) {
return undefined;
}
return async (pluginId: string): Promise<T> => {
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;
};
}
}
@@ -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';
@@ -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<void>;
}
export interface BackendRegisterInit {
id: string;
consumes: Set<ServiceRef<unknown>>;
provides: Set<ServiceRef<unknown>>;
deps: { [name: string]: ServiceRef<unknown> };
init: (deps: { [name: string]: unknown }) => Promise<void>;
}
/**
* @public
*/
export interface CreateBackendOptions {
apis: AnyServiceFactory[];
}
export type ServiceHolder = {
get<T>(api: ServiceRef<T>): FactoryFunc<T> | undefined;
};
/**
* @public
*/
export function createBackend(options?: CreateBackendOptions): Backend {
// TODO: merge with provided APIs
return new BackstageBackend([
...defaultServiceFactories,
...(options?.apis ?? []),
]);
}
+1
View File
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+5
View File
@@ -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.
+38
View File
@@ -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"
]
}
+27
View File
@@ -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();
+1
View File
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
+19
View File
@@ -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/<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)
+197
View File
@@ -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<TExtensionPoint>(
ref: ServiceRef<TExtensionPoint>,
impl: TExtensionPoint,
): void;
// (undocumented)
registerInit<
Deps extends {
[name in string]: unknown;
},
>(options: {
deps: {
[name in keyof Deps]: ServiceRef<Deps[name]>;
};
init: (deps: Deps) => Promise<void>;
}): void;
}
// @public (undocumented)
export interface BackendModuleConfig<TOptions> {
// (undocumented)
moduleId: string;
// (undocumented)
pluginId: string;
// (undocumented)
register(
reg: Omit<BackendInitRegistry, 'registerExtensionPoint'>,
options: TOptions,
): void;
}
// @public (undocumented)
export interface BackendPluginConfig<TOptions> {
// (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<PluginCacheManager>;
// @public (undocumented)
export const configServiceRef: ServiceRef<Config>;
// @public (undocumented)
export function createBackendModule<TOptions>(
config: BackendModuleConfig<TOptions>,
): (option: TOptions) => BackendRegistrable;
// @public (undocumented)
export function createBackendPlugin<TOptions>(
config: BackendPluginConfig<TOptions>,
): (option: TOptions) => BackendRegistrable;
// @public (undocumented)
export function createExtensionPoint<T>(options: {
id: string;
}): ExtensionPoint<T>;
// @public (undocumented)
export function createServiceFactory<
Api,
Impl extends Api,
Deps extends {
[name in string]: unknown;
},
>(factory: ServiceFactory<Api, Impl, Deps>): ServiceFactory<Api, Api, {}>;
// @public (undocumented)
export function createServiceRef<T>(options: { id: string }): ServiceRef<T>;
// @public (undocumented)
export const databaseServiceRef: ServiceRef<PluginDatabaseManager>;
// @public (undocumented)
export type DepsToDepFactories<T> = {
[key in keyof T]: (pluginId: string) => Promise<T[key]>;
};
// @public (undocumented)
export const discoveryServiceRef: ServiceRef<PluginEndpointDiscovery>;
// @public
export type ExtensionPoint<T> = {
id: string;
T: T;
toString(): string;
$$ref: 'extension-point';
};
// @public (undocumented)
export type FactoryFunc<Impl> = (pluginId: string) => Promise<Impl>;
// @public (undocumented)
export interface HttpRouterService {
// (undocumented)
use(handler: Handler): void;
}
// @public (undocumented)
export const httpRouterServiceRef: ServiceRef<HttpRouterService>;
// @public (undocumented)
export interface Logger {
// (undocumented)
child(fields: { [name: string]: string }): Logger;
// (undocumented)
info(message: string): void;
}
// @public (undocumented)
export const loggerServiceRef: ServiceRef<Logger>;
// @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<PluginTaskScheduler>;
// @public (undocumented)
export type ServiceFactory<
TApi,
TImpl extends TApi,
TDeps extends {
[name in string]: unknown;
},
> = {
service: ServiceRef<TApi>;
deps: TypesToServiceRef<TDeps>;
factory(deps: DepsToDepFactories<TDeps>): Promise<FactoryFunc<TImpl>>;
};
// @public
export type ServiceRef<T> = {
id: string;
T: T;
toString(): string;
$$ref: 'service';
};
// @public (undocumented)
export const tokenManagerServiceRef: ServiceRef<TokenManager>;
// @public (undocumented)
export type TypesToServiceRef<T> = {
[key in keyof T]: ServiceRef<T[key]>;
};
// @public (undocumented)
export const urlReaderServiceRef: ServiceRef<UrlReader>;
```
+53
View File
@@ -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"
]
}
+24
View File
@@ -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';
@@ -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<PluginCacheManager>({
id: 'core.cache',
});
@@ -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<Config>({
id: 'core.config',
});
@@ -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<PluginDatabaseManager>({
id: 'core.database',
});
@@ -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<PluginEndpointDiscovery>({
id: 'core.discovery',
});
@@ -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<HttpRouterService>({
id: 'core.httpRouter',
});
@@ -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';
@@ -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<Logger>({
id: 'core.logger',
});
@@ -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',
});
@@ -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<PluginTaskScheduler>({
id: 'core.scheduler',
});
@@ -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<TokenManager>({
id: 'core.tokenManager',
});
@@ -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<UrlReader>({
id: 'core.urlReader',
});
@@ -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';
@@ -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)],
});
}
@@ -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';
@@ -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';
@@ -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<T> = {
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<T> = { [key in keyof T]: ServiceRef<T[key]> };
/** @public */
export type DepsToDepFactories<T> = {
[key in keyof T]: (pluginId: string) => Promise<T[key]>;
};
/** @public */
export type FactoryFunc<Impl> = (pluginId: string) => Promise<Impl>;
/** @public */
export type ServiceFactory<
TApi,
TImpl extends TApi,
TDeps extends { [name in string]: unknown },
> = {
service: ServiceRef<TApi>;
deps: TypesToServiceRef<TDeps>;
factory(deps: DepsToDepFactories<TDeps>): Promise<FactoryFunc<TImpl>>;
};
/** @public */
export type AnyServiceFactory = ServiceFactory<
unknown,
unknown,
{ [key in string]: unknown }
>;
/**
* @public
*/
export function createServiceRef<T>(options: { id: string }): ServiceRef<T> {
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<Api, Impl, Deps>): ServiceFactory<Api, Api, {}> {
return factory;
}
@@ -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';
@@ -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<T> = {
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<T>(options: {
id: string;
}): ExtensionPoint<T> {
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<TExtensionPoint>(
ref: ServiceRef<TExtensionPoint>,
impl: TExtensionPoint,
): void;
registerInit<Deps extends { [name in string]: unknown }>(options: {
deps: { [name in keyof Deps]: ServiceRef<Deps[name]> };
init: (deps: Deps) => Promise<void>;
}): void;
}
/** @public */
export interface BackendRegistrable {
id: string;
register(reg: BackendInitRegistry): void;
}
/** @public */
export interface BackendPluginConfig<TOptions> {
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<TOptions>(
config: BackendPluginConfig<TOptions>,
): (option: TOptions) => BackendRegistrable {
return options => ({
id: config.id,
register(register) {
return config.register(register, options);
},
});
}
/** @public */
export interface BackendModuleConfig<TOptions> {
pluginId: string;
moduleId: string;
register(
reg: Omit<BackendInitRegistry, 'registerExtensionPoint'>,
options: TOptions,
): void;
}
// TODO: Make option optional in the returned factory if they are indeed optional
/** @public */
export function createBackendModule<TOptions>(
config: BackendModuleConfig<TOptions>,
): (option: TOptions) => BackendRegistrable {
return options => ({
id: `${config.pluginId}.${config.moduleId}`,
register(register) {
// TODO: Hide registerExtensionPoint
return config.register(register, options);
},
});
}
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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": {
@@ -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: \[\}/,
);
});
+1 -1
View File
@@ -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",
@@ -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",
@@ -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;
})
@@ -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>): 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', () => {
@@ -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,
+45 -131
View File
@@ -5,18 +5,34 @@
```ts
/// <reference types="node" />
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;
}): void;
}
// @alpha
@@ -199,6 +223,9 @@ export type CatalogEnvironment = {
export type CatalogPermissionRule<TParams extends unknown[] = unknown[]> =
PermissionRule<Entity, EntitiesSearchFilter, 'catalog-entity', TParams>;
// @alpha
export const catalogPlugin: (option: unknown) => BackendRegistrable;
// @public (undocumented)
export interface CatalogProcessingEngine {
// (undocumented)
@@ -207,86 +234,25 @@ export interface CatalogProcessingEngine {
stop(): Promise<void>;
}
// @public (undocumented)
export type CatalogProcessor = {
getProcessorName(): string;
readLocation?(
location: LocationSpec,
optional: boolean,
emit: CatalogProcessorEmit,
parser: CatalogProcessorParser,
cache: CatalogProcessorCache,
): Promise<boolean>;
preProcessEntity?(
entity: Entity,
location: LocationSpec,
emit: CatalogProcessorEmit,
originLocation: LocationSpec,
cache: CatalogProcessorCache,
): Promise<Entity>;
validateEntityKind?(entity: Entity): Promise<boolean>;
postProcessEntity?(
entity: Entity,
location: LocationSpec,
emit: CatalogProcessorEmit,
cache: CatalogProcessorCache,
): Promise<Entity>;
};
export { CatalogProcessor };
// @public
export interface CatalogProcessorCache {
get<ItemType extends JsonValue>(key: string): Promise<ItemType | undefined>;
set<ItemType extends JsonValue>(key: string, value: ItemType): Promise<void>;
}
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<CatalogProcessorResult>;
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<void>;
getProviderName(): string;
}
export { EntityProvider };
// @public
export interface EntityProviderConnection {
applyMutation(mutation: EntityProviderMutation): Promise<void>;
}
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 {
+3 -1
View File
@@ -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"
},
@@ -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
@@ -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.
+20 -1
View File
@@ -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';
@@ -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: {
@@ -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.
@@ -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 = {
@@ -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
@@ -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'];
@@ -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', () => {
@@ -29,7 +29,7 @@ import {
CatalogProcessor,
CatalogProcessorEmit,
LocationSpec,
} from '../../api';
} from '@backstage/plugin-catalog-node';
/** @public */
export class AnnotateLocationEntityProcessor implements CatalogProcessor {
@@ -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', () => {
@@ -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';
@@ -53,7 +53,7 @@ import {
CatalogProcessorEmit,
LocationSpec,
processingResult,
} from '../../api';
} from '@backstage/plugin-catalog-node';
/** @public */
export class BuiltinKindsEntityProcessor implements CatalogProcessor {
@@ -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 () => {
@@ -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 {
@@ -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';
@@ -19,7 +19,7 @@ import {
CatalogProcessorEntityResult,
CatalogProcessorErrorResult,
CatalogProcessorResult,
} from '../../api';
} from '@backstage/plugin-catalog-node';
import path from 'path';
import { defaultEntityDataParser } from '../util/parse';
@@ -24,7 +24,7 @@ import {
CatalogProcessorParser,
LocationSpec,
processingResult,
} from '../../api';
} from '@backstage/plugin-catalog-node';
const glob = promisify(g);
@@ -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', () => {
@@ -22,7 +22,7 @@ import {
CatalogProcessor,
CatalogProcessorEmit,
LocationSpec,
} from '../../api';
} from '@backstage/plugin-catalog-node';
export function toAbsoluteUrl(
integrations: ScmIntegrationRegistry,
@@ -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/,
);
});
@@ -24,7 +24,7 @@ import {
CatalogProcessorEmit,
LocationSpec,
processingResult,
} from '../../api';
} from '@backstage/plugin-catalog-node';
/** @public */
export type PlaceholderResolverRead = (url: string) => Promise<Buffer>;
@@ -28,7 +28,7 @@ import {
CatalogProcessorEntityResult,
CatalogProcessorErrorResult,
CatalogProcessorResult,
} from '../../api';
} from '@backstage/plugin-catalog-node';
import { defaultEntityDataParser } from '../util/parse';
import { UrlReaderProcessor } from './UrlReaderProcessor';

Some files were not shown because too many files have changed in this diff Show More