Merge pull request #29740 from backstage/freben/catalog2

catalog-backend: remove all deprecated exports, remove old backend system support
This commit is contained in:
Fredrik Adelöw
2025-04-29 11:47:51 +02:00
committed by GitHub
36 changed files with 255 additions and 1614 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': minor
---
**BREAKING ALPHA**: You can no longer import the catalog plugin from the `/alpha` export; please use the regular root default export instead.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-node': minor
---
Added `parseEntityYaml` from `@backstage/plugin-catalog-backend`, to make it more easily usable by custom plugins and modules
+66
View File
@@ -0,0 +1,66 @@
---
'@backstage/plugin-catalog-backend': major
---
**BREAKING**: Removed all deprecated exports, and removed support for the old backend system.
It also removes the `CodeOwnersProcessor` from the default set of processors, because it is expensive to run and has vague semantics. You need to update your backend to add it to the `catalogProcessingExtensionPoint` if you wish to continue using it.
The following removed exports are available from `@backstage/plugin-catalog-node`:
- `locationSpecToMetadataName`
- `locationSpecToLocationEntity`
- `processingResult`
- `EntitiesSearchFilter`
- `EntityFilter`
- `DeferredEntity`
- `EntityRelationSpec`
- `CatalogProcessor`
- `CatalogProcessorParser`
- `CatalogProcessorCache`
- `CatalogProcessorEmit`
- `CatalogProcessorLocationResult`
- `CatalogProcessorEntityResult`
- `CatalogProcessorRelationResult`
- `CatalogProcessorErrorResult`
- `CatalogProcessorRefreshKeysResult`
- `CatalogProcessorResult`
- `EntityProvider`
- `EntityProviderConnection`
- `EntityProviderMutation`
- `AnalyzeOptions`
- `LocationAnalyzer`
- `ScmLocationAnalyzer`
- `PlaceholderResolver`
- `PlaceholderResolverParams`
- `PlaceholderResolverRead`
- `PlaceholderResolverResolveUrl`
- `parseEntityYaml`
The following removed exports are available from `@backstage/plugin-catalog-common`:
- `LocationSpec`
- `AnalyzeLocationRequest`
- `AnalyzeLocationResponse`
- `AnalyzeLocationExistingEntity`
- `AnalyzeLocationGenerateEntity`
- `AnalyzeLocationEntityField`
The following removed exports are instead implemented in the new backend system by `@backstage/plugin-search-backend-module-catalog`:
- `defaultCatalogCollatorEntityTransformer`
- `CatalogCollatorEntityTransformer`
- `DefaultCatalogCollator`
The following exports are removed without a direct replacement:
- `DefaultCatalogCollatorFactory`
- `DefaultCatalogCollatorFactoryOptions`
- `LocationEntityProcessor`
- `LocationEntityProcessorOptions`
- `CatalogBuilder`
- `CatalogEnvironment`
- `CatalogPermissionRuleInput`
- `CatalogProcessingEngine`
- `createRandomProcessingInterval`
- `ProcessingIntervalFunction`
+1 -1
View File
@@ -17,5 +17,5 @@
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
backend.add(import('../src/alpha'));
backend.add(import('../src'));
backend.start();
+1 -4
View File
@@ -61,7 +61,6 @@
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/backend-common": "^0.25.0",
"@backstage/backend-openapi-utils": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/catalog-client": "workspace:^",
@@ -74,11 +73,8 @@
"@backstage/plugin-events-node": "workspace:^",
"@backstage/plugin-permission-common": "workspace:^",
"@backstage/plugin-permission-node": "workspace:^",
"@backstage/plugin-search-backend-module-catalog": "workspace:^",
"@backstage/plugin-search-common": "workspace:^",
"@backstage/types": "workspace:^",
"@opentelemetry/api": "^1.9.0",
"@types/express": "^4.17.6",
"codeowners-utils": "^1.0.2",
"core-js": "^3.6.5",
"express": "^4.17.1",
@@ -105,6 +101,7 @@
"@backstage/repo-tools": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@types/core-js": "^2.5.4",
"@types/express": "^4.17.6",
"@types/git-url-parse": "^9.0.0",
"@types/glob": "^8.0.0",
"@types/lodash": "^4.14.151",
@@ -3,7 +3,6 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { BackendFeature } from '@backstage/backend-plugin-api';
import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common';
import { Conditions } from '@backstage/plugin-permission-node';
import { EntitiesSearchFilter } from '@backstage/plugin-catalog-node';
@@ -87,10 +86,6 @@ export const createCatalogPermissionRule: <
rule: PermissionRule<Entity, EntitiesSearchFilter, 'catalog-entity', TParams>,
) => PermissionRule<Entity, EntitiesSearchFilter, 'catalog-entity', TParams>;
// @alpha (undocumented)
const _feature: BackendFeature;
export default _feature;
// @alpha
export const permissionRules: {
hasAnnotation: PermissionRule<
+31 -391
View File
@@ -3,104 +3,36 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { AnalyzeLocationEntityField as AnalyzeLocationEntityField_2 } from '@backstage/plugin-catalog-common';
import { AnalyzeLocationExistingEntity as AnalyzeLocationExistingEntity_2 } from '@backstage/plugin-catalog-common';
import { AnalyzeLocationGenerateEntity as AnalyzeLocationGenerateEntity_2 } from '@backstage/plugin-catalog-common';
import { AnalyzeLocationRequest as AnalyzeLocationRequest_2 } from '@backstage/plugin-catalog-common';
import { AnalyzeLocationResponse as AnalyzeLocationResponse_2 } from '@backstage/plugin-catalog-common';
import { AnalyzeOptions as AnalyzeOptions_2 } from '@backstage/plugin-catalog-node';
import { AuditorService } from '@backstage/backend-plugin-api';
import { AuthService } from '@backstage/backend-plugin-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
import { CatalogApi } from '@backstage/catalog-client';
import { CatalogCollatorEntityTransformer as CatalogCollatorEntityTransformer_2 } from '@backstage/plugin-search-backend-module-catalog';
import { CatalogEntityDocument } from '@backstage/plugin-catalog-common';
import { CatalogProcessor as CatalogProcessor_2 } from '@backstage/plugin-catalog-node';
import { CatalogProcessorCache as CatalogProcessorCache_2 } from '@backstage/plugin-catalog-node';
import { CatalogProcessorEmit as CatalogProcessorEmit_2 } from '@backstage/plugin-catalog-node';
import { CatalogProcessorEntityResult as CatalogProcessorEntityResult_2 } from '@backstage/plugin-catalog-node';
import { CatalogProcessorErrorResult as CatalogProcessorErrorResult_2 } from '@backstage/plugin-catalog-node';
import { CatalogProcessorLocationResult as CatalogProcessorLocationResult_2 } from '@backstage/plugin-catalog-node';
import { CatalogProcessorParser as CatalogProcessorParser_2 } from '@backstage/plugin-catalog-node';
import { CatalogProcessorRefreshKeysResult as CatalogProcessorRefreshKeysResult_2 } from '@backstage/plugin-catalog-node';
import { CatalogProcessorRelationResult as CatalogProcessorRelationResult_2 } from '@backstage/plugin-catalog-node';
import { CatalogProcessorResult as CatalogProcessorResult_2 } from '@backstage/plugin-catalog-node';
import { CatalogProcessor } from '@backstage/plugin-catalog-node';
import { CatalogProcessorCache } from '@backstage/plugin-catalog-node';
import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node';
import { CatalogProcessorParser } from '@backstage/plugin-catalog-node';
import { Config } from '@backstage/config';
import { DatabaseService } from '@backstage/backend-plugin-api';
import { DeferredEntity as DeferredEntity_2 } from '@backstage/plugin-catalog-node';
import { DiscoveryService } from '@backstage/backend-plugin-api';
import { DocumentCollatorFactory } from '@backstage/plugin-search-common';
import { EntitiesSearchFilter as EntitiesSearchFilter_2 } from '@backstage/plugin-catalog-node';
import { Entity } from '@backstage/catalog-model';
import { EntityFilter as EntityFilter_2 } from '@backstage/plugin-catalog-node';
import { EntityPolicy } from '@backstage/catalog-model';
import { EntityProvider as EntityProvider_2 } from '@backstage/plugin-catalog-node';
import { EntityProviderConnection as EntityProviderConnection_2 } from '@backstage/plugin-catalog-node';
import { EntityProviderMutation as EntityProviderMutation_2 } from '@backstage/plugin-catalog-node';
import { EntityRelationSpec as EntityRelationSpec_2 } from '@backstage/plugin-catalog-node';
import { EventBroker } from '@backstage/plugin-events-node';
import { EventsService } from '@backstage/plugin-events-node';
import { GetEntitiesRequest } from '@backstage/catalog-client';
import { HttpAuthService } from '@backstage/backend-plugin-api';
import { LocationAnalyzer as LocationAnalyzer_2 } from '@backstage/plugin-catalog-node';
import { LocationSpec as LocationSpec_2 } from '@backstage/plugin-catalog-common';
import { locationSpecToLocationEntity as locationSpecToLocationEntity_2 } from '@backstage/plugin-catalog-node';
import { locationSpecToMetadataName as locationSpecToMetadataName_2 } from '@backstage/plugin-catalog-node';
import { LocationSpec } from '@backstage/plugin-catalog-common';
import { LoggerService } from '@backstage/backend-plugin-api';
import { Permission } from '@backstage/plugin-permission-common';
import { PermissionAuthorizer } from '@backstage/plugin-permission-common';
import { PermissionRule } from '@backstage/plugin-permission-node';
import { PermissionRuleParams } from '@backstage/plugin-permission-common';
import { PermissionsRegistryService } from '@backstage/backend-plugin-api';
import { PermissionsService } from '@backstage/backend-plugin-api';
import { PlaceholderResolver as PlaceholderResolver_2 } from '@backstage/plugin-catalog-node';
import { PlaceholderResolverParams as PlaceholderResolverParams_2 } from '@backstage/plugin-catalog-node';
import { PlaceholderResolverRead as PlaceholderResolverRead_2 } from '@backstage/plugin-catalog-node';
import { PlaceholderResolverResolveUrl as PlaceholderResolverResolveUrl_2 } from '@backstage/plugin-catalog-node';
import { Readable } from 'stream';
import { RootConfigService } from '@backstage/backend-plugin-api';
import { Router } from 'express';
import { SchedulerService } from '@backstage/backend-plugin-api';
import { PlaceholderResolver } from '@backstage/plugin-catalog-node';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { ScmLocationAnalyzer as ScmLocationAnalyzer_2 } from '@backstage/plugin-catalog-node';
import { TokenManager } from '@backstage/backend-common';
import { UrlReaderService } from '@backstage/backend-plugin-api';
import { Validators } from '@backstage/catalog-model';
// @public @deprecated
export type AnalyzeLocationEntityField = AnalyzeLocationEntityField_2;
// @public @deprecated
export type AnalyzeLocationExistingEntity = AnalyzeLocationExistingEntity_2;
// @public @deprecated
export type AnalyzeLocationGenerateEntity = AnalyzeLocationGenerateEntity_2;
// @public @deprecated (undocumented)
export type AnalyzeLocationRequest = AnalyzeLocationRequest_2;
// @public @deprecated (undocumented)
export type AnalyzeLocationResponse = AnalyzeLocationResponse_2;
// @public @deprecated (undocumented)
export type AnalyzeOptions = AnalyzeOptions_2;
// @public (undocumented)
export class AnnotateLocationEntityProcessor implements CatalogProcessor_2 {
export class AnnotateLocationEntityProcessor implements CatalogProcessor {
constructor(options: { integrations: ScmIntegrationRegistry });
// (undocumented)
getProcessorName(): string;
// (undocumented)
preProcessEntity(
entity: Entity,
location: LocationSpec_2,
_: CatalogProcessorEmit_2,
originLocation: LocationSpec_2,
location: LocationSpec,
_: CatalogProcessorEmit,
originLocation: LocationSpec,
): Promise<Entity>;
}
// @public (undocumented)
export class AnnotateScmSlugEntityProcessor implements CatalogProcessor_2 {
export class AnnotateScmSlugEntityProcessor implements CatalogProcessor {
constructor(opts: {
scmIntegrationRegistry: ScmIntegrationRegistry;
kinds?: string[];
@@ -115,18 +47,18 @@ export class AnnotateScmSlugEntityProcessor implements CatalogProcessor_2 {
// (undocumented)
getProcessorName(): string;
// (undocumented)
preProcessEntity(entity: Entity, location: LocationSpec_2): Promise<Entity>;
preProcessEntity(entity: Entity, location: LocationSpec): Promise<Entity>;
}
// @public (undocumented)
export class BuiltinKindsEntityProcessor implements CatalogProcessor_2 {
export class BuiltinKindsEntityProcessor implements CatalogProcessor {
// (undocumented)
getProcessorName(): string;
// (undocumented)
postProcessEntity(
entity: Entity,
_location: LocationSpec_2,
emit: CatalogProcessorEmit_2,
_location: LocationSpec,
emit: CatalogProcessorEmit,
): Promise<Entity>;
// (undocumented)
validateEntityKind(entity: Entity): Promise<boolean>;
@@ -138,126 +70,12 @@ export const CATALOG_CONFLICTS_TOPIC = 'experimental.catalog.conflict';
// @public (undocumented)
export const CATALOG_ERRORS_TOPIC = 'experimental.catalog.errors';
// @public @deprecated
export class CatalogBuilder {
addEntityPolicy(
...policies: Array<EntityPolicy | Array<EntityPolicy>>
): CatalogBuilder;
addEntityProvider(
...providers: Array<EntityProvider_2 | Array<EntityProvider_2>>
): CatalogBuilder;
addLocationAnalyzers(
...analyzers: Array<ScmLocationAnalyzer_2 | Array<ScmLocationAnalyzer_2>>
): CatalogBuilder;
addPermissionRules(
...permissionRules: Array<
CatalogPermissionRuleInput | Array<CatalogPermissionRuleInput>
>
): this;
addPermissions(...permissions: Array<Permission | Array<Permission>>): this;
addProcessor(
...processors: Array<CatalogProcessor_2 | Array<CatalogProcessor_2>>
): CatalogBuilder;
build(): Promise<{
processingEngine: CatalogProcessingEngine;
router: Router;
}>;
static create(env: CatalogEnvironment): CatalogBuilder;
getDefaultProcessors(): CatalogProcessor_2[];
replaceEntityPolicies(policies: EntityPolicy[]): CatalogBuilder;
replaceProcessors(processors: CatalogProcessor_2[]): CatalogBuilder;
setAllowedLocationTypes(allowedLocationTypes: string[]): CatalogBuilder;
setEntityDataParser(parser: CatalogProcessorParser_2): CatalogBuilder;
setEventBroker(broker: EventBroker | EventsService): CatalogBuilder;
setFieldFormatValidators(validators: Partial<Validators>): CatalogBuilder;
setLocationAnalyzer(locationAnalyzer: LocationAnalyzer_2): CatalogBuilder;
setPlaceholderResolver(
key: string,
resolver: PlaceholderResolver_2,
): CatalogBuilder;
setProcessingInterval(
processingInterval: ProcessingIntervalFunction,
): CatalogBuilder;
setProcessingIntervalSeconds(seconds: number): CatalogBuilder;
// (undocumented)
subscribe(options: {
onProcessingError: (event: {
unprocessedEntity: Entity;
errors: Error[];
}) => Promise<void> | void;
}): void;
useLegacySingleProcessorValidation(): this;
}
// @public @deprecated (undocumented)
export type CatalogCollatorEntityTransformer =
CatalogCollatorEntityTransformer_2;
// @public @deprecated (undocumented)
export type CatalogEnvironment = {
logger: LoggerService;
database: DatabaseService;
config: RootConfigService;
reader: UrlReaderService;
permissions: PermissionsService | PermissionAuthorizer;
permissionsRegistry?: PermissionsRegistryService;
scheduler?: SchedulerService;
discovery?: DiscoveryService;
auth?: AuthService;
httpAuth?: HttpAuthService;
auditor?: AuditorService;
};
// @public
export type CatalogPermissionRuleInput<
TParams extends PermissionRuleParams = PermissionRuleParams,
> = PermissionRule<Entity, EntitiesSearchFilter_2, 'catalog-entity', TParams>;
// @public
const catalogPlugin: BackendFeature;
export default catalogPlugin;
// @public
export interface CatalogProcessingEngine {
// (undocumented)
start(): Promise<void>;
// (undocumented)
stop(): Promise<void>;
}
// @public @deprecated (undocumented)
export type CatalogProcessor = CatalogProcessor_2;
// @public @deprecated (undocumented)
export type CatalogProcessorCache = CatalogProcessorCache_2;
// @public @deprecated (undocumented)
export type CatalogProcessorEmit = CatalogProcessorEmit_2;
// @public @deprecated (undocumented)
export type CatalogProcessorEntityResult = CatalogProcessorEntityResult_2;
// @public @deprecated (undocumented)
export type CatalogProcessorErrorResult = CatalogProcessorErrorResult_2;
// @public @deprecated (undocumented)
export type CatalogProcessorLocationResult = CatalogProcessorLocationResult_2;
// @public @deprecated (undocumented)
export type CatalogProcessorParser = CatalogProcessorParser_2;
// @public @deprecated (undocumented)
export type CatalogProcessorRefreshKeysResult =
CatalogProcessorRefreshKeysResult_2;
// @public @deprecated (undocumented)
export type CatalogProcessorRelationResult = CatalogProcessorRelationResult_2;
// @public @deprecated (undocumented)
export type CatalogProcessorResult = CatalogProcessorResult_2;
// @public (undocumented)
export class CodeOwnersProcessor implements CatalogProcessor_2 {
export class CodeOwnersProcessor implements CatalogProcessor {
constructor(options: {
integrations: ScmIntegrationRegistry;
logger: LoggerService;
@@ -274,227 +92,49 @@ export class CodeOwnersProcessor implements CatalogProcessor_2 {
// (undocumented)
getProcessorName(): string;
// (undocumented)
preProcessEntity(entity: Entity, location: LocationSpec_2): Promise<Entity>;
preProcessEntity(entity: Entity, location: LocationSpec): Promise<Entity>;
}
// @public
export function createRandomProcessingInterval(options: {
minSeconds: number;
maxSeconds: number;
}): ProcessingIntervalFunction;
// @public @deprecated (undocumented)
export class DefaultCatalogCollator {
constructor(options: {
discovery: DiscoveryService;
tokenManager: TokenManager;
locationTemplate?: string;
filter?: GetEntitiesRequest['filter'];
catalogClient?: CatalogApi;
});
// (undocumented)
protected applyArgsToFormat(
format: string,
args: Record<string, string>,
): string;
// (undocumented)
protected readonly catalogClient: CatalogApi;
// (undocumented)
protected discovery: DiscoveryService;
// (undocumented)
execute(): Promise<CatalogEntityDocument[]>;
// (undocumented)
protected filter?: GetEntitiesRequest['filter'];
// (undocumented)
static fromConfig(
_config: Config,
options: {
discovery: DiscoveryService;
tokenManager: TokenManager;
filter?: GetEntitiesRequest['filter'];
},
): DefaultCatalogCollator;
// (undocumented)
protected locationTemplate: string;
// (undocumented)
protected tokenManager: TokenManager;
// (undocumented)
readonly type: string;
// (undocumented)
readonly visibilityPermission: Permission;
}
// @public @deprecated (undocumented)
export const defaultCatalogCollatorEntityTransformer: CatalogCollatorEntityTransformer_2;
// @public @deprecated (undocumented)
export class DefaultCatalogCollatorFactory implements DocumentCollatorFactory {
// (undocumented)
static fromConfig(
configRoot: Config,
options: DefaultCatalogCollatorFactoryOptions,
): DefaultCatalogCollatorFactory;
// (undocumented)
getCollator(): Promise<Readable>;
// (undocumented)
readonly type = 'software-catalog';
// (undocumented)
readonly visibilityPermission: Permission;
}
// @public @deprecated (undocumented)
export type DefaultCatalogCollatorFactoryOptions = {
auth?: AuthService;
discovery: DiscoveryService;
tokenManager?: TokenManager;
locationTemplate?: string;
filter?: GetEntitiesRequest['filter'];
batchSize?: number;
catalogClient?: CatalogApi;
entityTransformer?: CatalogCollatorEntityTransformer;
};
// @public @deprecated (undocumented)
export type DeferredEntity = DeferredEntity_2;
// @public @deprecated (undocumented)
export type EntitiesSearchFilter = EntitiesSearchFilter_2;
// @public @deprecated (undocumented)
export type EntityFilter = EntityFilter_2;
// @public @deprecated (undocumented)
export type EntityProvider = EntityProvider_2;
// @public @deprecated (undocumented)
export type EntityProviderConnection = EntityProviderConnection_2;
// @public @deprecated (undocumented)
export type EntityProviderMutation = EntityProviderMutation_2;
// @public @deprecated (undocumented)
export type EntityRelationSpec = EntityRelationSpec_2;
// @public (undocumented)
export class FileReaderProcessor implements CatalogProcessor_2 {
export class FileReaderProcessor implements CatalogProcessor {
// (undocumented)
getProcessorName(): string;
// (undocumented)
readLocation(
location: LocationSpec_2,
location: LocationSpec,
optional: boolean,
emit: CatalogProcessorEmit_2,
parser: CatalogProcessorParser_2,
emit: CatalogProcessorEmit,
parser: CatalogProcessorParser,
): Promise<boolean>;
}
// @public @deprecated (undocumented)
export type LocationAnalyzer = LocationAnalyzer_2;
// @public @deprecated
export class LocationEntityProcessor implements CatalogProcessor_2 {
constructor(options: LocationEntityProcessorOptions);
// (undocumented)
getProcessorName(): string;
// (undocumented)
postProcessEntity(
entity: Entity,
location: LocationSpec_2,
emit: CatalogProcessorEmit_2,
): Promise<Entity>;
}
// @public @deprecated (undocumented)
export type LocationEntityProcessorOptions = {
integrations: ScmIntegrationRegistry;
};
// @public @deprecated
export type LocationSpec = LocationSpec_2;
// @public @deprecated (undocumented)
export const locationSpecToLocationEntity: typeof locationSpecToLocationEntity_2;
// @public @deprecated (undocumented)
export const locationSpecToMetadataName: typeof locationSpecToMetadataName_2;
// @public (undocumented)
export function parseEntityYaml(
data: Buffer,
location: LocationSpec_2,
): Iterable<CatalogProcessorResult_2>;
// @public
export class PlaceholderProcessor implements CatalogProcessor_2 {
export class PlaceholderProcessor implements CatalogProcessor {
constructor(options: PlaceholderProcessorOptions);
// (undocumented)
getProcessorName(): string;
// (undocumented)
preProcessEntity(
entity: Entity,
location: LocationSpec_2,
emit: CatalogProcessorEmit_2,
location: LocationSpec,
emit: CatalogProcessorEmit,
): Promise<Entity>;
}
// @public (undocumented)
export type PlaceholderProcessorOptions = {
resolvers: Record<string, PlaceholderResolver_2>;
resolvers: Record<string, PlaceholderResolver>;
reader: UrlReaderService;
integrations: ScmIntegrationRegistry;
};
// @public @deprecated (undocumented)
export type PlaceholderResolver = PlaceholderResolver_2;
// @public @deprecated (undocumented)
export type PlaceholderResolverParams = PlaceholderResolverParams_2;
// @public @deprecated (undocumented)
export type PlaceholderResolverRead = PlaceholderResolverRead_2;
// @public @deprecated (undocumented)
export type PlaceholderResolverResolveUrl = PlaceholderResolverResolveUrl_2;
// @public
export type ProcessingIntervalFunction = () => number;
// @public @deprecated (undocumented)
export const processingResult: Readonly<{
readonly notFoundError: (
atLocation: LocationSpec_2,
message: string,
) => CatalogProcessorResult_2;
readonly inputError: (
atLocation: LocationSpec_2,
message: string,
) => CatalogProcessorResult_2;
readonly generalError: (
atLocation: LocationSpec_2,
message: string,
) => CatalogProcessorResult_2;
readonly location: (newLocation: LocationSpec_2) => CatalogProcessorResult_2;
readonly entity: (
atLocation: LocationSpec_2,
newEntity: Entity,
options?: {
locationKey?: string | null;
},
) => CatalogProcessorResult_2;
readonly relation: (spec: EntityRelationSpec_2) => CatalogProcessorResult_2;
readonly refresh: (key: string) => CatalogProcessorResult_2;
}>;
// @public @deprecated (undocumented)
export type ScmLocationAnalyzer = ScmLocationAnalyzer_2;
// @public
export function transformLegacyPolicyToProcessor(
policy: EntityPolicy,
): CatalogProcessor_2;
): CatalogProcessor;
// @public (undocumented)
export class UrlReaderProcessor implements CatalogProcessor_2 {
export class UrlReaderProcessor implements CatalogProcessor {
constructor(options: {
reader: UrlReaderService;
logger: LoggerService;
@@ -504,11 +144,11 @@ export class UrlReaderProcessor implements CatalogProcessor_2 {
getProcessorName(): string;
// (undocumented)
readLocation(
location: LocationSpec_2,
location: LocationSpec,
optional: boolean,
emit: CatalogProcessorEmit_2,
parser: CatalogProcessorParser_2,
cache: CatalogProcessorCache_2,
emit: CatalogProcessorEmit,
parser: CatalogProcessorParser,
cache: CatalogProcessorCache,
): Promise<boolean>;
}
```
-6
View File
@@ -14,10 +14,4 @@
* limitations under the License.
*/
import { catalogPlugin } from './service/CatalogPlugin';
/** @alpha */
const _feature = catalogPlugin;
export default _feature;
export * from './permissions';
@@ -32,7 +32,7 @@ import {
DbRefreshStateRow,
DbRelationsRow,
} from './tables';
import { createRandomProcessingInterval } from '../processing';
import { createRandomProcessingInterval } from '../processing/refresh';
import { timestampToDateTime } from './conversion';
import { generateStableHash } from './util';
import { LoggerService } from '@backstage/backend-plugin-api';
@@ -19,7 +19,7 @@ import { ConflictError } from '@backstage/errors';
import { DeferredEntity } from '@backstage/plugin-catalog-node';
import { Knex } from 'knex';
import lodash from 'lodash';
import { ProcessingIntervalFunction } from '../processing';
import { ProcessingIntervalFunction } from '../processing/refresh';
import { rethrowError, timestampToDateTime } from './conversion';
import { initDatabaseMetrics } from './metrics';
import {
-502
View File
@@ -1,502 +0,0 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
TokenManager,
createLegacyAuthAdapters,
} from '@backstage/backend-common';
import { AuthService, DiscoveryService } from '@backstage/backend-plugin-api';
import {
CatalogApi,
CatalogClient,
EntityFilterQuery,
GetEntitiesRequest,
} from '@backstage/catalog-client';
import {
Entity,
isGroupEntity,
isUserEntity,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import {
CatalogEntityDocument,
type AnalyzeLocationEntityField as _AnalyzeLocationEntityField,
type AnalyzeLocationExistingEntity as _AnalyzeLocationExistingEntity,
type AnalyzeLocationGenerateEntity as _AnalyzeLocationGenerateEntity,
type AnalyzeLocationRequest as _AnalyzeLocationRequest,
type AnalyzeLocationResponse as _AnalyzeLocationResponse,
type LocationSpec as _LocationSpec,
} from '@backstage/plugin-catalog-common';
import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';
import {
locationSpecToMetadataName as _locationSpecToMetadataName,
locationSpecToLocationEntity as _locationSpecToLocationEntity,
processingResult as _processingResult,
type EntitiesSearchFilter as _EntitiesSearchFilter,
type EntityFilter as _EntityFilter,
type DeferredEntity as _DeferredEntity,
type EntityRelationSpec as _EntityRelationSpec,
type CatalogProcessor as _CatalogProcessor,
type CatalogProcessorParser as _CatalogProcessorParser,
type CatalogProcessorCache as _CatalogProcessorCache,
type CatalogProcessorEmit as _CatalogProcessorEmit,
type CatalogProcessorLocationResult as _CatalogProcessorLocationResult,
type CatalogProcessorEntityResult as _CatalogProcessorEntityResult,
type CatalogProcessorRelationResult as _CatalogProcessorRelationResult,
type CatalogProcessorErrorResult as _CatalogProcessorErrorResult,
type CatalogProcessorRefreshKeysResult as _CatalogProcessorRefreshKeysResult,
type CatalogProcessorResult as _CatalogProcessorResult,
type EntityProvider as _EntityProvider,
type EntityProviderConnection as _EntityProviderConnection,
type EntityProviderMutation as _EntityProviderMutation,
type AnalyzeOptions as _AnalyzeOptions,
type PlaceholderResolver as _PlaceholderResolver,
type PlaceholderResolverParams as _PlaceholderResolverParams,
type PlaceholderResolverRead as _PlaceholderResolverRead,
type PlaceholderResolverResolveUrl as _PlaceholderResolverResolveUrl,
type LocationAnalyzer as _LocationAnalyzer,
type ScmLocationAnalyzer as _ScmLocationAnalyzer,
} from '@backstage/plugin-catalog-node';
import { Permission } from '@backstage/plugin-permission-common';
import {
defaultCatalogCollatorEntityTransformer as _defaultCatalogCollatorEntityTransformer,
type CatalogCollatorEntityTransformer as _CatalogCollatorEntityTransformer,
} from '@backstage/plugin-search-backend-module-catalog';
import { DocumentCollatorFactory } from '@backstage/plugin-search-common';
import { Readable } from 'stream';
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export const locationSpecToMetadataName = _locationSpecToMetadataName;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export const locationSpecToLocationEntity = _locationSpecToLocationEntity;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export const processingResult = _processingResult;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type EntitiesSearchFilter = _EntitiesSearchFilter;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type EntityFilter = _EntityFilter;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type DeferredEntity = _DeferredEntity;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type EntityRelationSpec = _EntityRelationSpec;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type CatalogProcessor = _CatalogProcessor;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type CatalogProcessorParser = _CatalogProcessorParser;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type CatalogProcessorCache = _CatalogProcessorCache;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type CatalogProcessorEmit = _CatalogProcessorEmit;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type CatalogProcessorLocationResult = _CatalogProcessorLocationResult;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type CatalogProcessorEntityResult = _CatalogProcessorEntityResult;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type CatalogProcessorRelationResult = _CatalogProcessorRelationResult;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type CatalogProcessorErrorResult = _CatalogProcessorErrorResult;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type CatalogProcessorRefreshKeysResult =
_CatalogProcessorRefreshKeysResult;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type CatalogProcessorResult = _CatalogProcessorResult;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type EntityProvider = _EntityProvider;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type EntityProviderConnection = _EntityProviderConnection;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type EntityProviderMutation = _EntityProviderMutation;
/**
* Holds the entity location information.
*
* @remarks
*
* `presence` flag: when using repo importer plugin, location is being created before the component yaml file is merged to the main branch.
* This flag is then set to indicate that the file can be not present.
* default value: 'required'.
*
* @public
* @deprecated use the same type from `@backstage/plugin-catalog-common` instead
*/
export type LocationSpec = _LocationSpec;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type AnalyzeOptions = _AnalyzeOptions;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type LocationAnalyzer = _LocationAnalyzer;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type ScmLocationAnalyzer = _ScmLocationAnalyzer;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type PlaceholderResolver = _PlaceholderResolver;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type PlaceholderResolverParams = _PlaceholderResolverParams;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type PlaceholderResolverRead = _PlaceholderResolverRead;
/**
* @public
* @deprecated import from `@backstage/plugin-catalog-node` instead
*/
export type PlaceholderResolverResolveUrl = _PlaceholderResolverResolveUrl;
/**
* @public
* @deprecated use the same type from `@backstage/plugin-catalog-common` instead
*/
export type AnalyzeLocationRequest = _AnalyzeLocationRequest;
/**
* @public
* @deprecated use the same type from `@backstage/plugin-catalog-common` instead
*/
export type AnalyzeLocationResponse = _AnalyzeLocationResponse;
/**
* If the folder pointed to already contained catalog info yaml files, they are
* read and emitted like this so that the frontend can inform the user that it
* located them and can make sure to register them as well if they weren't
* already
* @public
* @deprecated use the same type from `@backstage/plugin-catalog-common` instead
*/
export type AnalyzeLocationExistingEntity = _AnalyzeLocationExistingEntity;
/**
* This is some form of representation of what the analyzer could deduce.
* We should probably have a chat about how this can best be conveyed to
* the frontend. It'll probably contain a (possibly incomplete) entity, plus
* enough info for the frontend to know what form data to show to the user
* for overriding/completing the info.
* @public
* @deprecated use the same type from `@backstage/plugin-catalog-common` instead
*/
export type AnalyzeLocationGenerateEntity = _AnalyzeLocationGenerateEntity;
/**
*
* This is where I get really vague. Something like this perhaps? Or it could be
* something like a json-schema that contains enough info for the frontend to
* be able to present a form and explanations
* @public
* @deprecated use the same type from `@backstage/plugin-catalog-common` instead
*/
export type AnalyzeLocationEntityField = _AnalyzeLocationEntityField;
namespace search {
const configKey = 'search.collators.catalog';
const defaults = {
schedule: {
frequency: { minutes: 10 },
timeout: { minutes: 15 },
initialDelay: { seconds: 3 },
},
collatorOptions: {
locationTemplate: '/catalog/:namespace/:kind/:name',
filter: undefined,
batchSize: 500,
},
};
export const readCollatorConfigOptions = (
configRoot: Config,
): {
locationTemplate: string;
filter: EntityFilterQuery | undefined;
batchSize: number;
} => {
const config = configRoot.getOptionalConfig(configKey);
if (!config) {
return defaults.collatorOptions;
}
return {
locationTemplate:
config.getOptionalString('locationTemplate') ??
defaults.collatorOptions.locationTemplate,
filter:
config.getOptional<EntityFilterQuery>('filter') ??
defaults.collatorOptions.filter,
batchSize:
config.getOptionalNumber('batchSize') ??
defaults.collatorOptions.batchSize,
};
};
export const getDocumentText = (entity: Entity): string => {
const documentTexts: string[] = [];
if (entity.metadata.description) {
documentTexts.push(entity.metadata.description);
}
if (isUserEntity(entity) || isGroupEntity(entity)) {
if (entity.spec?.profile?.displayName) {
documentTexts.push(entity.spec.profile.displayName);
}
}
if (isUserEntity(entity)) {
if (entity.spec?.profile?.email) {
documentTexts.push(entity.spec.profile.email);
}
}
return documentTexts.join(' : ');
};
}
/**
* @public
* @deprecated import from `@backstage/plugin-search-backend-module-catalog` instead
*/
export const defaultCatalogCollatorEntityTransformer =
_defaultCatalogCollatorEntityTransformer;
/**
* @public
* @deprecated This is no longer supported since the new backend system migration
*/
export class DefaultCatalogCollatorFactory implements DocumentCollatorFactory {
public readonly type = 'software-catalog';
public readonly visibilityPermission: Permission =
catalogEntityReadPermission;
private locationTemplate: string;
private filter?: GetEntitiesRequest['filter'];
private batchSize: number;
private readonly catalogClient: CatalogApi;
private entityTransformer: CatalogCollatorEntityTransformer;
private auth: AuthService;
static fromConfig(
configRoot: Config,
options: DefaultCatalogCollatorFactoryOptions,
) {
const configOptions = search.readCollatorConfigOptions(configRoot);
const { auth: adaptedAuth } = createLegacyAuthAdapters({
auth: options.auth,
discovery: options.discovery,
tokenManager: options.tokenManager,
});
return new DefaultCatalogCollatorFactory({
locationTemplate:
options.locationTemplate ?? configOptions.locationTemplate,
filter: options.filter ?? configOptions.filter,
batchSize: options.batchSize ?? configOptions.batchSize,
entityTransformer: options.entityTransformer,
auth: adaptedAuth,
discovery: options.discovery,
catalogClient: options.catalogClient,
});
}
private constructor(options: {
locationTemplate: string;
filter: GetEntitiesRequest['filter'];
batchSize: number;
entityTransformer?: CatalogCollatorEntityTransformer;
auth: AuthService;
discovery: DiscoveryService;
catalogClient?: CatalogApi;
}) {
const {
auth,
batchSize,
discovery,
locationTemplate,
filter,
catalogClient,
entityTransformer,
} = options;
this.locationTemplate = locationTemplate;
this.filter = filter;
this.batchSize = batchSize;
this.catalogClient =
catalogClient || new CatalogClient({ discoveryApi: discovery });
this.entityTransformer =
entityTransformer ?? defaultCatalogCollatorEntityTransformer;
this.auth = auth;
}
async getCollator(): Promise<Readable> {
return Readable.from(this.execute());
}
private async *execute(): AsyncGenerator<CatalogEntityDocument> {
let entitiesRetrieved = 0;
let cursor: string | undefined = undefined;
do {
const { token } = await this.auth.getPluginRequestToken({
onBehalfOf: await this.auth.getOwnServiceCredentials(),
targetPluginId: 'catalog',
});
const response = await this.catalogClient.queryEntities(
{
filter: this.filter,
limit: this.batchSize,
...(cursor ? { cursor } : {}),
},
{ token },
);
cursor = response.pageInfo.nextCursor;
entitiesRetrieved += response.items.length;
for (const entity of response.items) {
yield {
...this.entityTransformer(entity),
authorization: {
resourceRef: stringifyEntityRef(entity),
},
location: this.applyArgsToFormat(this.locationTemplate, {
namespace: entity.metadata.namespace || 'default',
kind: entity.kind,
name: entity.metadata.name,
}),
};
}
} while (cursor);
}
private applyArgsToFormat(
format: string,
args: Record<string, string>,
): string {
let formatted = format;
for (const [key, value] of Object.entries(args)) {
formatted = formatted.replace(`:${key}`, value);
}
return formatted.toLowerCase();
}
}
/**
* @public
* @deprecated This is no longer supported since the new backend system migration
*/
export type DefaultCatalogCollatorFactoryOptions = {
auth?: AuthService;
discovery: DiscoveryService;
tokenManager?: TokenManager;
/**
* @deprecated Use the config key `search.collators.catalog.locationTemplate` instead.
*/
locationTemplate?: string;
/**
* @deprecated Use the config key `search.collators.catalog.filter` instead.
*/
filter?: GetEntitiesRequest['filter'];
/**
* @deprecated Use the config key `search.collators.catalog.batchSize` instead.
*/
batchSize?: number;
// TODO(freben): Change to required CatalogService instead when fully migrated to the new backend system.
catalogClient?: CatalogApi;
/**
* Allows you to customize how entities are shaped into documents.
*/
entityTransformer?: CatalogCollatorEntityTransformer;
};
/**
* @public
* @deprecated import from `@backstage/plugin-search-backend-module-catalog` instead
*/
export type CatalogCollatorEntityTransformer =
_CatalogCollatorEntityTransformer;
-5
View File
@@ -22,9 +22,4 @@
export { catalogPlugin as default } from './service/CatalogPlugin';
export * from './processors';
export * from './processing';
export * from './search';
export * from './service';
export * from './deprecated';
export * from './constants';
export * from './util';
@@ -84,7 +84,6 @@ function addProcessorAttributes(
);
}
/** @public */
export class DefaultCatalogProcessingOrchestrator
implements CatalogProcessingOrchestrator
{
@@ -1,19 +0,0 @@
/*
* 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 { CatalogProcessingEngine } from './types';
export { createRandomProcessingInterval } from './refresh';
export type { ProcessingIntervalFunction } from './refresh';
@@ -16,14 +16,12 @@
/**
* Function that returns the catalog processing interval in seconds.
* @public
*/
export type ProcessingIntervalFunction = () => number;
/**
* Creates a function that returns a random processing interval between minSeconds and maxSeconds.
* @returns A {@link ProcessingIntervalFunction} that provides the next processing interval
* @public
*/
export function createRandomProcessingInterval(options: {
minSeconds: number;
@@ -1,87 +0,0 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ConfigReader } from '@backstage/config';
import {
ScmIntegrations,
ScmIntegrationRegistry,
} from '@backstage/integration';
import path from 'path';
import { toAbsoluteUrl } from './LocationEntityProcessor';
import { LocationSpec } from '@backstage/plugin-catalog-common';
describe('LocationEntityProcessor', () => {
describe('toAbsoluteUrl', () => {
it('handles files', () => {
const integrations = {} as unknown as ScmIntegrationRegistry;
const base: LocationSpec = {
type: 'file',
target: `some${path.sep}path${path.sep}catalog-info.yaml`,
};
expect(toAbsoluteUrl(integrations, base, `.${path.sep}c`)).toBe(
`some${path.sep}path${path.sep}c`,
);
expect(toAbsoluteUrl(integrations, base, `${path.sep}c`)).toBe(
`${path.sep}c`,
);
});
it('handles urls', () => {
const integrations = ScmIntegrations.fromConfig(new ConfigReader({}));
const base: LocationSpec = {
type: 'url',
target: 'http://a.com/b/catalog-info.yaml',
};
jest.spyOn(integrations, 'resolveUrl');
expect(toAbsoluteUrl(integrations, base, './c/d')).toBe(
'http://a.com/b/c/d',
);
expect(toAbsoluteUrl(integrations, base, 'c/d')).toBe(
'http://a.com/b/c/d',
);
expect(toAbsoluteUrl(integrations, base, 'http://b.com/z')).toBe(
'http://b.com/z',
);
expect(integrations.resolveUrl).toHaveBeenCalledTimes(3);
});
it('handles azure urls specifically', () => {
const integrations = ScmIntegrations.fromConfig(
new ConfigReader({
integrations: {
azure: [{ host: 'dev.azure.com' }],
},
}),
);
expect(
toAbsoluteUrl(
integrations,
{
type: 'url',
target:
'https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml',
},
'./a.yaml',
),
).toBe(
'https://dev.azure.com/organization/project/_git/repository?path=%2Fa.yaml',
);
});
});
});
@@ -1,110 +0,0 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Entity, LocationEntity } from '@backstage/catalog-model';
import { ScmIntegrationRegistry } from '@backstage/integration';
import path from 'path';
import { LocationSpec } from '@backstage/plugin-catalog-common';
import {
processingResult,
CatalogProcessor,
CatalogProcessorEmit,
} from '@backstage/plugin-catalog-node';
export function toAbsoluteUrl(
integrations: ScmIntegrationRegistry,
base: LocationSpec,
target: string,
): string {
try {
if (base.type === 'file') {
if (target.startsWith('.')) {
return path.join(path.dirname(base.target), target);
}
return target;
}
return integrations.resolveUrl({ url: target, base: base.target });
} catch (e) {
return target;
}
}
/**
* @public
* @deprecated This processor should no longer be used
*/
export type LocationEntityProcessorOptions = {
integrations: ScmIntegrationRegistry;
};
/**
* Legacy processor, should not be used.
*
* @remarks
*
* In the old catalog architecture, this processor translated Location entities
* into URLs that should be fetched. This is no longer needed since the engine
* handles this internally.
*
* @public
* @deprecated This processor should no longer be used
*/
export class LocationEntityProcessor implements CatalogProcessor {
constructor(private readonly options: LocationEntityProcessorOptions) {}
getProcessorName(): string {
return 'LocationEntityProcessor';
}
async postProcessEntity(
entity: Entity,
location: LocationSpec,
emit: CatalogProcessorEmit,
): Promise<Entity> {
if (entity.kind === 'Location') {
const locationEntity = entity as LocationEntity;
const type = locationEntity.spec.type || location.type;
if (type === 'file' && location.target.endsWith(path.sep)) {
emit(
processingResult.inputError(
location,
`LocationEntityProcessor cannot handle ${type} type location with target ${location.target} that ends with a path separator`,
),
);
}
const targets = new Array<string>();
if (locationEntity.spec.target) {
targets.push(locationEntity.spec.target);
}
if (locationEntity.spec.targets) {
targets.push(...locationEntity.spec.targets);
}
for (const maybeRelativeTarget of targets) {
const target = toAbsoluteUrl(
this.options.integrations,
location,
maybeRelativeTarget,
);
emit(processingResult.location({ type, target }));
}
}
return entity;
}
}
@@ -19,8 +19,6 @@ export { AnnotateScmSlugEntityProcessor } from './AnnotateScmSlugEntityProcessor
export { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor';
export { CodeOwnersProcessor } from './CodeOwnersProcessor';
export { FileReaderProcessor } from './FileReaderProcessor';
export { LocationEntityProcessor } from './LocationEntityProcessor';
export type { LocationEntityProcessorOptions } from './LocationEntityProcessor';
export { PlaceholderProcessor } from './PlaceholderProcessor';
export type { PlaceholderProcessorOptions } from './PlaceholderProcessor';
export { UrlReaderProcessor } from './UrlReaderProcessor';
@@ -1,159 +0,0 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TokenManager } from '@backstage/backend-common';
import {
mockServices,
registerMswTestHooks,
} from '@backstage/backend-test-utils';
import { Entity } from '@backstage/catalog-model';
import { DefaultCatalogCollator } from './DefaultCatalogCollator';
import { setupServer } from 'msw/node';
import { rest } from 'msw';
import { ConfigReader } from '@backstage/config';
const server = setupServer();
const expectedEntities: Entity[] = [
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'test-entity',
description: 'The expected description',
},
spec: {
type: 'some-type',
lifecycle: 'experimental',
owner: 'someone',
},
},
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
title: 'Test Entity',
name: 'test-entity-2',
description: 'The expected description 2',
},
spec: {
type: 'some-type',
lifecycle: 'experimental',
owner: 'someone',
},
},
];
describe('DefaultCatalogCollator', () => {
const mockDiscovery = mockServices.discovery.mock({
getBaseUrl: async () => 'http://localhost:7007',
});
let mockTokenManager: jest.Mocked<TokenManager>;
let collator: DefaultCatalogCollator;
registerMswTestHooks(server);
beforeAll(() => {
mockTokenManager = {
getToken: jest.fn().mockResolvedValue({ token: '' }),
authenticate: jest.fn(),
};
collator = new DefaultCatalogCollator({
discovery: mockDiscovery,
tokenManager: mockTokenManager,
});
});
beforeEach(() => {
server.use(
rest.get('http://localhost:7007/entities', (req, res, ctx) => {
if (req.url.searchParams.has('filter')) {
const filter = req.url.searchParams.get('filter');
if (filter === 'kind=Foo,kind=Bar') {
// When filtering on the 'Foo,Bar' kinds we simply return no items, to simulate a filter
return res(ctx.json([]));
}
throw new Error('Unexpected filter parameter');
}
return res(ctx.json(expectedEntities));
}),
);
});
afterAll(() => {
jest.useRealTimers();
});
it('fetches from the configured catalog service', async () => {
const documents = await collator.execute();
expect(mockDiscovery.getBaseUrl).toHaveBeenCalledWith('catalog');
expect(documents).toHaveLength(expectedEntities.length);
});
it('maps a returned entity to an expected CatalogEntityDocument', async () => {
const documents = await collator.execute();
expect(documents[0]).toMatchObject({
title: expectedEntities[0].metadata.name,
location: '/catalog/default/component/test-entity',
text: expectedEntities[0].metadata.description,
namespace: 'default',
componentType: expectedEntities[0]!.spec!.type,
lifecycle: expectedEntities[0]!.spec!.lifecycle,
owner: expectedEntities[0]!.spec!.owner,
authorization: {
resourceRef: 'component:default/test-entity',
},
});
expect(documents[1]).toMatchObject({
title: `${expectedEntities[1].metadata.title} (${expectedEntities[1].metadata.name})`,
location: '/catalog/default/component/test-entity-2',
text: expectedEntities[1].metadata.description,
namespace: 'default',
componentType: expectedEntities[1]!.spec!.type,
lifecycle: expectedEntities[1]!.spec!.lifecycle,
owner: expectedEntities[1]!.spec!.owner,
authorization: {
resourceRef: 'component:default/test-entity-2',
},
});
});
it('maps a returned entity with a custom locationTemplate', async () => {
// Provide an alternate location template.
collator = new DefaultCatalogCollator({
discovery: mockDiscovery,
tokenManager: mockTokenManager,
locationTemplate: '/software/:name',
});
const documents = await collator.execute();
expect(documents[0]).toMatchObject({
location: '/software/test-entity',
});
});
it('allows filtering of the retrieved catalog entities', async () => {
// Provide an alternate location template.
collator = DefaultCatalogCollator.fromConfig(new ConfigReader({}), {
discovery: mockDiscovery,
tokenManager: mockTokenManager,
filter: {
kind: ['Foo', 'Bar'],
},
});
const documents = await collator.execute();
// The simulated 'Foo,Bar' filter should return in an empty list
expect(documents).toHaveLength(0);
});
});
@@ -1,137 +0,0 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TokenManager } from '@backstage/backend-common';
import {
Entity,
isUserEntity,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import {
CatalogApi,
CatalogClient,
GetEntitiesRequest,
} from '@backstage/catalog-client';
import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';
import { CatalogEntityDocument } from '@backstage/plugin-catalog-common';
import { Permission } from '@backstage/plugin-permission-common';
import { DiscoveryService } from '@backstage/backend-plugin-api';
/**
* @public
* @deprecated Upgrade to a more recent `@backstage/plugin-search-backend-node` and
* use `DefaultCatalogCollatorFactory` instead.
*/
export class DefaultCatalogCollator {
protected discovery: DiscoveryService;
protected locationTemplate: string;
protected filter?: GetEntitiesRequest['filter'];
protected readonly catalogClient: CatalogApi;
public readonly type: string = 'software-catalog';
public readonly visibilityPermission: Permission =
catalogEntityReadPermission;
protected tokenManager: TokenManager;
static fromConfig(
_config: Config,
options: {
discovery: DiscoveryService;
tokenManager: TokenManager;
filter?: GetEntitiesRequest['filter'];
},
) {
return new DefaultCatalogCollator({
...options,
});
}
constructor(options: {
discovery: DiscoveryService;
tokenManager: TokenManager;
locationTemplate?: string;
filter?: GetEntitiesRequest['filter'];
catalogClient?: CatalogApi;
}) {
const { discovery, locationTemplate, filter, catalogClient, tokenManager } =
options;
this.discovery = discovery;
this.locationTemplate =
locationTemplate || '/catalog/:namespace/:kind/:name';
this.filter = filter;
this.catalogClient =
catalogClient || new CatalogClient({ discoveryApi: discovery });
this.tokenManager = tokenManager;
}
protected applyArgsToFormat(
format: string,
args: Record<string, string>,
): string {
let formatted = format;
for (const [key, value] of Object.entries(args)) {
formatted = formatted.replace(`:${key}`, value);
}
return formatted.toLowerCase();
}
private getDocumentText(entity: Entity): string {
let documentText = entity.metadata.description || '';
if (isUserEntity(entity)) {
if (entity.spec?.profile?.displayName && documentText) {
// combine displayName and description
const displayName = entity.spec?.profile?.displayName;
documentText = displayName.concat(' : ', documentText);
} else {
documentText = entity.spec?.profile?.displayName || documentText;
}
}
return documentText;
}
async execute() {
const { token } = await this.tokenManager.getToken();
const response = await this.catalogClient.getEntities(
{
filter: this.filter,
},
{ token },
);
return response.items.map((entity: Entity): CatalogEntityDocument => {
return {
title: entity.metadata.title
? `${entity.metadata.title} (${entity.metadata.name})`
: entity.metadata.name,
location: this.applyArgsToFormat(this.locationTemplate, {
namespace: entity.metadata.namespace || 'default',
kind: entity.kind,
name: entity.metadata.name,
}),
text: this.getDocumentText(entity),
componentType: entity.spec?.type?.toString() || 'other',
type: entity.spec?.type?.toString() || 'other',
namespace: entity.metadata.namespace || 'default',
kind: entity.kind,
lifecycle: (entity.spec?.lifecycle as string) || '',
owner: (entity.spec?.owner as string) || '',
authorization: {
resourceRef: stringifyEntityRef(entity),
},
};
});
}
}
@@ -1,20 +0,0 @@
/*
* 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.
*/
/**
* todo(backstage/techdocs-core): stop exporting this in a future release.
*/
export { DefaultCatalogCollator } from './DefaultCatalogCollator';
@@ -14,10 +14,6 @@
* limitations under the License.
*/
import {
createLegacyAuthAdapters,
HostDiscovery,
} from '@backstage/backend-common';
import {
DefaultNamespaceEntityPolicy,
Entity,
@@ -38,7 +34,6 @@ import {
AuditorService,
AuthService,
DatabaseService,
DiscoveryService,
HttpAuthService,
LoggerService,
PermissionsRegistryService,
@@ -55,7 +50,6 @@ import {
import {
CatalogProcessor,
CatalogProcessorParser,
EntitiesSearchFilter,
EntityProvider,
LocationAnalyzer,
PlaceholderResolver,
@@ -65,13 +59,11 @@ import { EventBroker, EventsService } from '@backstage/plugin-events-node';
import {
Permission,
PermissionAuthorizer,
PermissionRuleParams,
toPermissionEvaluator,
} from '@backstage/plugin-permission-common';
import {
createConditionTransformer,
createPermissionIntegrationRouter,
PermissionRule,
} from '@backstage/plugin-permission-node';
import { durationToMilliseconds } from '@backstage/types';
import { DefaultCatalogDatabase } from '../database/DefaultCatalogDatabase';
@@ -81,11 +73,11 @@ import { applyDatabaseMigrations } from '../database/migrations';
import { DefaultCatalogRulesEnforcer } from '../ingestion/CatalogRules';
import { RepoLocationAnalyzer } from '../ingestion/LocationAnalyzer';
import { permissionRules as catalogPermissionRules } from '../permissions/rules';
import { CatalogProcessingEngine } from '../processing/types';
import {
CatalogProcessingEngine,
createRandomProcessingInterval,
ProcessingIntervalFunction,
} from '../processing';
} from '../processing/refresh';
import { connectEntityProviders } from '../processing/connectEntityProviders';
import { evictEntitiesFromOrphanedProviders } from '../processing/evictEntitiesFromOrphanedProviders';
import { DefaultCatalogProcessingEngine } from '../processing/DefaultCatalogProcessingEngine';
@@ -93,7 +85,6 @@ import { DefaultCatalogProcessingOrchestrator } from '../processing/DefaultCatal
import {
AnnotateLocationEntityProcessor,
BuiltinKindsEntityProcessor,
CodeOwnersProcessor,
FileReaderProcessor,
PlaceholderProcessor,
UrlReaderProcessor,
@@ -116,21 +107,11 @@ import { DefaultEntitiesCatalog } from './DefaultEntitiesCatalog';
import { DefaultLocationService } from './DefaultLocationService';
import { DefaultRefreshService } from './DefaultRefreshService';
import { entitiesResponseToObjects } from './response';
import { catalogEntityPermissionResourceRef } from '@backstage/plugin-catalog-node/alpha';
import {
catalogEntityPermissionResourceRef,
CatalogPermissionRuleInput,
} from '@backstage/plugin-catalog-node/alpha';
/**
* This is a duplicate of the alpha `CatalogPermissionRule` type, for use in the stable API.
*
* @public
*/
export type CatalogPermissionRuleInput<
TParams extends PermissionRuleParams = PermissionRuleParams,
> = PermissionRule<Entity, EntitiesSearchFilter, 'catalog-entity', TParams>;
/**
* @deprecated Please migrate to the new backend system as this will be removed in the future.
* @public
*/
export type CatalogEnvironment = {
logger: LoggerService;
database: DatabaseService;
@@ -139,9 +120,8 @@ export type CatalogEnvironment = {
permissions: PermissionsService | PermissionAuthorizer;
permissionsRegistry?: PermissionsRegistryService;
scheduler?: SchedulerService;
discovery?: DiscoveryService;
auth?: AuthService;
httpAuth?: HttpAuthService;
auth: AuthService;
httpAuth: HttpAuthService;
auditor?: AuditorService;
};
@@ -167,9 +147,6 @@ export type CatalogEnvironment = {
* - Processors can be added or replaced. These implement the functionality of
* reading, parsing, validating, and processing the entity data before it is
* persisted in the catalog.
*
* @public
* @deprecated Please migrate to the new backend system as this will be removed in the future.
*/
export class CatalogBuilder {
private readonly env: CatalogEnvironment;
@@ -380,7 +357,6 @@ export class CatalogBuilder {
return [
new FileReaderProcessor(),
new UrlReaderProcessor({ reader, logger, config }),
CodeOwnersProcessor.fromConfig(config, { logger, reader }),
new AnnotateLocationEntityProcessor({ integrations }),
];
}
@@ -482,15 +458,11 @@ export class CatalogBuilder {
permissions,
scheduler,
permissionsRegistry,
discovery = HostDiscovery.fromConfig(config),
auditor,
auth,
httpAuth,
} = this.env;
const { auth, httpAuth } = createLegacyAuthAdapters({
...this.env,
discovery,
});
const disableRelationsCompatibility = config.getOptionalBoolean(
'catalog.disableRelationsCompatibility',
);
@@ -35,13 +35,14 @@ import {
catalogModelExtensionPoint,
CatalogPermissionExtensionPoint,
catalogPermissionExtensionPoint,
CatalogPermissionRuleInput,
CatalogProcessingExtensionPoint,
catalogProcessingExtensionPoint,
} from '@backstage/plugin-catalog-node/alpha';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { Permission } from '@backstage/plugin-permission-common';
import { merge } from 'lodash';
import { CatalogBuilder, CatalogPermissionRuleInput } from './CatalogBuilder';
import { CatalogBuilder } from './CatalogBuilder';
class CatalogLocationsExtensionPointImpl
implements CatalogLocationsExtensionPoint
@@ -232,7 +233,6 @@ export const catalogPlugin = createBackendPlugin({
httpRouter: coreServices.httpRouter,
lifecycle: coreServices.rootLifecycle,
scheduler: coreServices.scheduler,
discovery: coreServices.discovery,
auth: coreServices.auth,
httpAuth: coreServices.httpAuth,
auditor: coreServices.auditor,
@@ -248,7 +248,6 @@ export const catalogPlugin = createBackendPlugin({
httpRouter,
lifecycle,
scheduler,
discovery,
auth,
httpAuth,
auditor,
@@ -262,7 +261,6 @@ export const catalogPlugin = createBackendPlugin({
database,
scheduler,
logger,
discovery,
auth,
httpAuth,
auditor,
@@ -65,9 +65,6 @@ import {
/**
* Options used by {@link createRouter}.
*
* @public
* @deprecated Please migrate to the new backend system as this will be removed in the future.
*/
export interface RouterOptions {
entitiesCatalog?: EntitiesCatalog;
@@ -1,21 +0,0 @@
/*
* 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 {
CatalogEnvironment,
CatalogPermissionRuleInput,
} from './CatalogBuilder';
export { CatalogBuilder } from './CatalogBuilder';
@@ -20,8 +20,6 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api';
/**
* Holds the information required to create a new location in the catalog location store.
*
* @public
*/
export interface LocationInput {
type: string;
@@ -30,7 +28,6 @@ export interface LocationInput {
/**
* The location service manages entity locations.
* @public
*/
export interface LocationService {
createLocation(
@@ -59,8 +56,6 @@ export interface LocationService {
/**
* Options for requesting a refresh of entities in the catalog.
*
* @public
*/
export type RefreshOptions = {
/** The reference to a single entity that should be refreshed */
@@ -70,8 +65,6 @@ export type RefreshOptions = {
/**
* A service that manages refreshes of entities in the catalog.
*
* @public
*/
export interface RefreshService {
/**
@@ -82,7 +75,6 @@ export interface RefreshService {
/**
* Interacts with the database to manage locations.
* @public
*/
export interface LocationStore {
createLocation(location: LocationInput): Promise<Location>;
@@ -47,14 +47,13 @@ import {
} from '../processing/DefaultCatalogProcessingEngine';
import { DefaultCatalogProcessingOrchestrator } from '../processing/DefaultCatalogProcessingOrchestrator';
import { connectEntityProviders } from '../processing/connectEntityProviders';
import { CatalogProcessingEngine } from '../processing';
import { CatalogProcessingEngine } from '../processing/types';
import { DefaultEntitiesCatalog } from '../service/DefaultEntitiesCatalog';
import { DefaultRefreshService } from '../service/DefaultRefreshService';
import { RefreshOptions, RefreshService } from '../service/types';
import { DefaultStitcher } from '../stitching/DefaultStitcher';
import { mockServices } from '@backstage/backend-test-utils';
import { mockServices, TestDatabases } from '@backstage/backend-test-utils';
import { LoggerService } from '@backstage/backend-plugin-api';
import { DatabaseManager } from '@backstage/backend-common';
import { entitiesResponseToObjects } from '../service/response';
import { deleteOrphanedEntities } from '../database/operations/util/deleteOrphanedEntities';
@@ -205,10 +204,10 @@ class TestHarness {
readonly #proxyProgressTracker: ProxyProgressTracker;
readonly #db: Knex;
static async create(options?: {
static async create(options: {
disableRelationsCompatibility?: boolean;
logger?: LoggerService;
db?: Knex;
db: Knex;
permissions?: PermissionEvaluator;
additionalProviders?: EntityProvider[];
processEntity?(
@@ -231,24 +230,19 @@ class TestHarness {
},
});
const logger = options?.logger ?? mockServices.logger.mock();
const db =
options?.db ??
(await DatabaseManager.fromConfig(config, { logger })
.forPlugin('catalog')
.getClient());
await applyDatabaseMigrations(db);
await applyDatabaseMigrations(options.db);
const catalogDatabase = new DefaultCatalogDatabase({
database: db,
database: options.db,
logger,
});
const providerDatabase = new DefaultProviderDatabase({
database: db,
database: options.db,
logger,
});
const processingDatabase = new DefaultProcessingDatabase({
database: db,
database: options.db,
logger,
refreshInterval: () => 0.05,
});
@@ -281,9 +275,12 @@ class TestHarness {
policy: EntityPolicies.allOf([]),
legacySingleProcessorValidation: false,
});
const stitcher = DefaultStitcher.fromConfig(config, { knex: db, logger });
const stitcher = DefaultStitcher.fromConfig(config, {
knex: options.db,
logger,
});
const catalog = new DefaultEntitiesCatalog({
database: db,
database: options.db,
logger,
stitcher,
disableRelationsCompatibility: options?.disableRelationsCompatibility,
@@ -296,7 +293,7 @@ class TestHarness {
config: new ConfigReader({}),
logger,
processingDatabase,
knex: db,
knex: options.db,
orchestrator,
stitcher,
createHash: () => createHash('sha1'),
@@ -333,7 +330,7 @@ class TestHarness {
refresh,
provider,
proxyProgressTracker,
db,
options.db,
);
}
@@ -444,10 +441,13 @@ class TestHarness {
}
describe('Catalog Backend Integration', () => {
const databases = TestDatabases.create({ ids: ['SQLITE_3'] });
it('should add entities and update errors', async () => {
let triggerError = false;
const harness = await TestHarness.create({
db: await databases.init('SQLITE_3'),
async processEntity(entity: Entity) {
if (triggerError) {
throw new Error('NOPE');
@@ -525,6 +525,7 @@ describe('Catalog Backend Integration', () => {
const generatedApis = ['api-1', 'api-2'];
const harness = await TestHarness.create({
db: await databases.init('SQLITE_3'),
async processEntity(
entity: Entity,
location: LocationSpec,
@@ -609,7 +610,9 @@ describe('Catalog Backend Integration', () => {
});
it('should not replace matching provided entities', async () => {
const harness = await TestHarness.create();
const harness = await TestHarness.create({
db: await databases.init('SQLITE_3'),
});
const entityA = {
apiVersion: 'backstage.io/v1alpha1',
@@ -669,6 +672,7 @@ describe('Catalog Backend Integration', () => {
}
const harness = await TestHarness.create({
db: await databases.init('SQLITE_3'),
async processEntity(
entity: Entity,
location: LocationSpec,
@@ -788,7 +792,9 @@ describe('Catalog Backend Integration', () => {
});
it('should reject insecure URLs', async () => {
const harness = await TestHarness.create();
const harness = await TestHarness.create({
db: await databases.init('SQLITE_3'),
});
await harness.setInputEntities([
{
@@ -827,7 +833,9 @@ describe('Catalog Backend Integration', () => {
});
it('should reject insecure location URLs', async () => {
const harness = await TestHarness.create();
const harness = await TestHarness.create({
db: await databases.init('SQLITE_3'),
});
await harness.setInputEntities([
{
@@ -854,6 +862,7 @@ describe('Catalog Backend Integration', () => {
it('should return valid responses in raw JSON mode', async () => {
const harness = await TestHarness.create({
db: await databases.init('SQLITE_3'),
disableRelationsCompatibility: true,
});
@@ -910,6 +919,7 @@ describe('Catalog Backend Integration', () => {
const secondProvider = new TestProvider('second');
const harness = await TestHarness.create({
db: await databases.init('SQLITE_3'),
additionalProviders: [firstProvider, secondProvider],
});
@@ -1066,7 +1076,10 @@ describe('Catalog Backend Integration', () => {
_emit: CatalogProcessorEmit,
) => entity,
);
const harness = await TestHarness.create({ processEntity });
const harness = await TestHarness.create({
db: await databases.init('SQLITE_3'),
processEntity,
});
processEntity.mockImplementation(async (entity, location, emit) => {
if (entity.metadata.name === entityA.metadata.name) {
@@ -1189,7 +1202,10 @@ describe('Catalog Backend Integration', () => {
_emit: CatalogProcessorEmit,
) => entity,
);
const harness = await TestHarness.create({ processEntity });
const harness = await TestHarness.create({
db: await databases.init('SQLITE_3'),
processEntity,
});
processEntity.mockImplementation(async (entity, location, emit) => {
if (entity.metadata.name === entityA.metadata.name) {
@@ -1246,7 +1262,10 @@ describe('Catalog Backend Integration', () => {
_emit: CatalogProcessorEmit,
) => entity,
);
const harness = await TestHarness.create({ processEntity });
const harness = await TestHarness.create({
db: await databases.init('SQLITE_3'),
processEntity,
});
processEntity.mockImplementation(async (entity, location, emit) => {
if (entity.metadata.name === entityA.metadata.name) {
@@ -32,7 +32,6 @@ export function locationSpecToMetadataName(location: LocationSpec) {
return `generated-${hash}`;
}
/** @public */
export function locationSpecToLocationEntity(opts: {
location: LocationSpec;
parentEntity?: Entity;
-17
View File
@@ -1,17 +0,0 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { parseEntityYaml } from './parse';
+1 -41
View File
@@ -14,51 +14,11 @@
* limitations under the License.
*/
import { Entity, stringifyLocationRef } from '@backstage/catalog-model';
import lodash from 'lodash';
import yaml from 'yaml';
import { LocationSpec } from '@backstage/plugin-catalog-common';
import {
CatalogProcessorParser,
CatalogProcessorResult,
processingResult,
parseEntityYaml,
} from '@backstage/plugin-catalog-node';
/** @public */
export function* parseEntityYaml(
data: Buffer,
location: LocationSpec,
): Iterable<CatalogProcessorResult> {
let documents: yaml.Document.Parsed[];
try {
documents = yaml.parseAllDocuments(data.toString('utf8')).filter(d => d);
} catch (e) {
const loc = stringifyLocationRef(location);
const message = `Failed to parse YAML at ${loc}, ${e}`;
yield processingResult.generalError(location, message);
return;
}
for (const document of documents) {
if (document.errors?.length) {
const loc = stringifyLocationRef(location);
const message = `YAML error at ${loc}, ${document.errors[0]}`;
yield processingResult.generalError(location, message);
} else {
const json = document.toJSON();
if (lodash.isPlainObject(json)) {
yield processingResult.entity(location, json as Entity);
} else if (json === null) {
// Ignore null values, these happen if there is an empty document in the
// YAML file, for example if --- is added to the end of the file.
} else {
const message = `Expected object at root, got ${typeof json}`;
yield processingResult.generalError(location, message);
}
}
}
}
export const defaultEntityDataParser: CatalogProcessorParser =
async function* defaultEntityDataParser({ data, location }) {
for (const e of parseEntityYaml(data, location)) {
+3 -1
View File
@@ -67,7 +67,9 @@
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-permission-common": "workspace:^",
"@backstage/plugin-permission-node": "workspace:^",
"@backstage/types": "workspace:^"
"@backstage/types": "workspace:^",
"lodash": "^4.17.21",
"yaml": "^2.0.0"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
+6
View File
@@ -297,6 +297,12 @@ export function locationSpecToLocationEntity(opts: {
// @public
export function locationSpecToMetadataName(location: LocationSpec_2): string;
// @public
export function parseEntityYaml(
data: string | Buffer,
location: LocationSpec_2,
): Iterable<CatalogProcessorResult>;
// @public (undocumented)
export type PlaceholderResolver = (
params: PlaceholderResolverParams,
@@ -24,3 +24,4 @@ export type {
LocationAnalyzer,
ScmLocationAnalyzer,
} from './types';
export { parseEntityYaml } from './parse';
@@ -0,0 +1,76 @@
/*
* Copyright 2020 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Entity, stringifyLocationRef } from '@backstage/catalog-model';
import lodash from 'lodash';
import yaml from 'yaml';
import { LocationSpec } from '@backstage/plugin-catalog-common';
import { CatalogProcessorResult } from '../api/processor';
import { processingResult } from '../api/processingResult';
/**
* A helper function that parses a YAML file, properly handling multiple
* documents in a single file.
*
* @public
* @remarks
*
* Each document is expected to be a valid Backstage entity. Each item in the
* iterable is in the form of an entity result if the document seemed like a
* valid object, or in the form of an error result if it seemed incorrect. This
* way, you can choose to retain a partial result if you want.
*
* Note that this function does NOT perform any validation of the entities. No
* assumptions whatsoever can be made on the emnitted entities except that they
* are valid JSON objects.
*/
export function* parseEntityYaml(
data: string | Buffer,
location: LocationSpec,
): Iterable<CatalogProcessorResult> {
let documents: yaml.Document.Parsed[];
try {
documents = yaml
.parseAllDocuments(
typeof data === 'string' ? data : data.toString('utf8'),
)
.filter(d => d);
} catch (e) {
const loc = stringifyLocationRef(location);
const message = `Failed to parse YAML at ${loc}, ${e}`;
yield processingResult.generalError(location, message);
return;
}
for (const document of documents) {
if (document.errors?.length) {
const loc = stringifyLocationRef(location);
const message = `YAML error at ${loc}, ${document.errors[0]}`;
yield processingResult.generalError(location, message);
} else {
const json = document.toJSON();
if (lodash.isPlainObject(json)) {
yield processingResult.entity(location, json as Entity);
} else if (json === null) {
// Ignore null values, these happen if there is an empty document in the
// YAML file, for example if --- is added to the end of the file.
} else {
const message = `Expected object at root, got ${typeof json}`;
yield processingResult.generalError(location, message);
}
}
}
}
+2 -3
View File
@@ -6016,7 +6016,6 @@ __metadata:
version: 0.0.0-use.local
resolution: "@backstage/plugin-catalog-backend@workspace:plugins/catalog-backend"
dependencies:
"@backstage/backend-common": "npm:^0.25.0"
"@backstage/backend-defaults": "workspace:^"
"@backstage/backend-openapi-utils": "workspace:^"
"@backstage/backend-plugin-api": "workspace:^"
@@ -6032,8 +6031,6 @@ __metadata:
"@backstage/plugin-events-node": "workspace:^"
"@backstage/plugin-permission-common": "workspace:^"
"@backstage/plugin-permission-node": "workspace:^"
"@backstage/plugin-search-backend-module-catalog": "workspace:^"
"@backstage/plugin-search-common": "workspace:^"
"@backstage/repo-tools": "workspace:^"
"@backstage/test-utils": "workspace:^"
"@backstage/types": "workspace:^"
@@ -6189,7 +6186,9 @@ __metadata:
"@backstage/plugin-permission-common": "workspace:^"
"@backstage/plugin-permission-node": "workspace:^"
"@backstage/types": "workspace:^"
lodash: "npm:^4.17.21"
msw: "npm:^1.0.0"
yaml: "npm:^2.0.0"
languageName: unknown
linkType: soft