From fbf2d1256b9c3bb80666ddcfb1a45f9c9ea1f56d Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 22 Sep 2022 00:17:52 +0200 Subject: [PATCH 01/35] move github search to the backend Signed-off-by: Kiss Miklos --- plugins/catalog-backend/package.json | 2 + .../src/ingestion/GitHubLocationAnalyzer.ts | 88 +++++++++++++++++++ .../src/ingestion/LocationAnalyzer.ts | 41 ++++++++- .../catalog-backend/src/ingestion/types.ts | 16 +++- .../src/service/CatalogBuilder.ts | 5 +- .../src/service/standaloneServer.ts | 1 + plugins/catalog-import/package.json | 1 + .../src/api/CatalogImportClient.ts | 86 ++---------------- yarn.lock | 5 +- 9 files changed, 159 insertions(+), 86 deletions(-) create mode 100644 plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index f5ab216a7f..1e2d732f35 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -38,6 +38,7 @@ "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", @@ -47,6 +48,7 @@ "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-search-common": "workspace:^", "@backstage/types": "workspace:^", + "@octokit/rest": "^19.0.4", "@types/express": "^4.17.6", "codeowners-utils": "^1.0.2", "core-js": "^3.6.5", diff --git a/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts new file mode 100644 index 0000000000..b96b50649e --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts @@ -0,0 +1,88 @@ +/* + * 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 { CatalogClient } from '@backstage/catalog-client'; +import { GitHubIntegration } from '@backstage/integration'; +import { DiscoveryApi } from '@backstage/plugin-permission-common'; +import { Octokit } from '@octokit/rest'; +import { trimEnd } from 'lodash'; +import { AnalyzeLocationExistingEntity, BaseLocationAnalyzer } from './types'; + +export type GitHubLocationAnalyzerOptions = { + integration: GitHubIntegration; + catalogFilename?: string; + + discovery: DiscoveryApi; +}; +export class GitHubLocationAnalyzer implements BaseLocationAnalyzer { + private readonly integration: GitHubIntegration; + private readonly catalogFilename: string; + private readonly discovery: DiscoveryApi; + + constructor(options: GitHubLocationAnalyzerOptions) { + this.integration = options.integration; + this.catalogFilename = options.catalogFilename || 'catalog-info.yaml'; + this.discovery = options.discovery; + } + + async analyze( + owner: string, + repo: string, + url: string, + ): Promise { + const octo = new Octokit({ + auth: this.integration.config.token, + baseUrl: this.integration.config.apiBaseUrl, + }); + const query = `filename:${this.catalogFilename} repo:${owner}/${repo} `; + + const catalogClient = new CatalogClient({ discoveryApi: this.discovery }); + + const searchResult = await octo.search.code({ q: query }).catch(e => { + throw new Error(`Couldn't search repository for metadata file, ${e}`); + }); + + const exists = searchResult.data.total_count > 0; + if (exists) { + const repoInformation = await octo.repos.get({ owner, repo }).catch(e => { + throw new Error(`Couldn't fetch repo data, ${e}`); + }); + const defaultBranch = repoInformation.data.default_branch; + + return await Promise.all( + searchResult.data.items + .map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`) + .map(async target => { + const result = await catalogClient.addLocation({ + type: 'url', + target, + dryRun: true, + }); + return { + target, + exists: result.exists, + entities: result.entities.map(e => ({ + kind: e.kind, + namespace: e.metadata.namespace ?? 'default', + name: e.metadata.name, + })), + }; + }), + ); + } + return []; + } +} diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index b84dff7de8..c7354a40bc 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -17,25 +17,40 @@ import { Logger } from 'winston'; import parseGitUrl from 'git-url-parse'; import { Entity } from '@backstage/catalog-model'; -import { ScmIntegrationRegistry } from '@backstage/integration'; +import { + GitHubIntegration, + ScmIntegrationRegistry, +} from '@backstage/integration'; import { AnalyzeLocationRequest, AnalyzeLocationResponse, LocationAnalyzer, } from './types'; +import { DiscoveryApi } from '@backstage/plugin-permission-common'; +import { GitHubLocationAnalyzer } from './GitHubLocationAnalyzer'; export class RepoLocationAnalyzer implements LocationAnalyzer { private readonly logger: Logger; private readonly scmIntegrations: ScmIntegrationRegistry; + private readonly discovery: DiscoveryApi; - constructor(logger: Logger, scmIntegrations: ScmIntegrationRegistry) { + constructor( + logger: Logger, + scmIntegrations: ScmIntegrationRegistry, + discovery: DiscoveryApi, + ) { this.logger = logger; this.scmIntegrations = scmIntegrations; + this.discovery = discovery; } async analyzeLocation( request: AnalyzeLocationRequest, ): Promise { + const integration = this.scmIntegrations.byUrl( + request.location.target, + ) as GitHubIntegration; const { owner, name } = parseGitUrl(request.location.target); + const entity: Entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', @@ -45,8 +60,8 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { spec: { type: 'other', lifecycle: 'unknown' }, }; - const integration = this.scmIntegrations.byUrl(request.location.target); let annotationPrefix; + let analyzer; switch (integration?.type) { case 'azure': annotationPrefix = 'dev.azure.com'; @@ -56,6 +71,10 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { break; case 'github': annotationPrefix = 'github.com'; + analyzer = new GitHubLocationAnalyzer({ + integration, + discovery: this.discovery, + }); break; case 'gitlab': annotationPrefix = 'gitlab.com'; @@ -63,6 +82,22 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { default: break; } + if (analyzer) { + const existingEntityFiles = await analyzer.analyze( + owner, + name, + request.location.target, + ); + if (existingEntityFiles.length > 0) { + this.logger.debug( + `entity for ${request.location.target} already exists.`, + ); + return { + existingEntityFiles, + generateEntities: [], + }; + } + } if (annotationPrefix) { entity.metadata.annotations = { diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 2602bd080f..149795d166 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; import { RecursivePartial } from '../util/RecursivePartial'; import { LocationSpec } from '@backstage/plugin-catalog-node'; @@ -50,9 +50,9 @@ export type AnalyzeLocationResponse = { * @public */ export type AnalyzeLocationExistingEntity = { - location: LocationSpec; - isRegistered: boolean; - entity: Entity; + target: string; + exists: boolean | undefined; + entities: CompoundEntityRef[]; }; /** @@ -98,3 +98,11 @@ export type AnalyzeLocationEntityField = { */ description: string; }; + +export interface BaseLocationAnalyzer { + analyze( + owner: string, + repo: string, + url: string, + ): Promise; +} diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index d47ace1454..2e20b0d4c3 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -96,6 +96,7 @@ import { RESOURCE_TYPE_CATALOG_ENTITY, } from '@backstage/plugin-catalog-common'; import { AuthorizedLocationService } from './AuthorizedLocationService'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; /** @public */ export type CatalogEnvironment = { @@ -104,6 +105,7 @@ export type CatalogEnvironment = { config: Config; reader: UrlReader; permissions: PermissionEvaluator | PermissionAuthorizer; + discovery: DiscoveryApi; }; /** @@ -478,7 +480,8 @@ export class CatalogBuilder { ); const locationAnalyzer = - this.locationAnalyzer ?? new RepoLocationAnalyzer(logger, integrations); + this.locationAnalyzer ?? + new RepoLocationAnalyzer(logger, integrations, this.env.discovery); const locationService = new AuthorizedLocationService( new DefaultLocationService(locationStore, orchestrator, { allowedLocationTypes: this.allowedLocationType, diff --git a/plugins/catalog-backend/src/service/standaloneServer.ts b/plugins/catalog-backend/src/service/standaloneServer.ts index e48515fb8b..51195ccdd7 100644 --- a/plugins/catalog-backend/src/service/standaloneServer.ts +++ b/plugins/catalog-backend/src/service/standaloneServer.ts @@ -70,6 +70,7 @@ export async function startStandaloneServer( config, reader, permissions, + discovery, }); const catalog = await builder.build(); diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 2a4ea4642c..055955c9c7 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -40,6 +40,7 @@ "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/integration-react": "workspace:^", + "@backstage/plugin-catalog-backend": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index c12e391ec9..0533b558c8 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -15,7 +15,6 @@ */ import { CatalogApi } from '@backstage/catalog-client'; -import { CompoundEntityRef } from '@backstage/catalog-model'; import { ConfigApi, DiscoveryApi, @@ -28,11 +27,10 @@ import { import { ScmAuthApi } from '@backstage/integration-react'; import { Octokit } from '@octokit/rest'; import { Base64 } from 'js-base64'; -import { PartialEntity } from '../types'; import { AnalyzeResult, CatalogImportApi } from './CatalogImportApi'; import { getGithubIntegrationConfig } from './GitHub'; -import { trimEnd } from 'lodash'; import { getBranchName, getCatalogFilename } from '../components/helpers'; +import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend'; /** * The default implementation of the {@link CatalogImportApi}. @@ -105,16 +103,14 @@ export class CatalogImportClient implements CatalogImportApi { ); } - // TODO: this could be part of the analyze-location endpoint - const locations = await this.checkGitHubForExistingCatalogInfo({ - ...ghConfig, - url, + const analyzation = await this.analyzeLocation({ + repo: url, }); - if (locations.length > 0) { + if (analyzation.existingEntityFiles.length > 0) { return { type: 'locations', - locations, + locations: analyzation.existingEntityFiles, }; } @@ -122,9 +118,7 @@ export class CatalogImportClient implements CatalogImportApi { type: 'repository', integrationType: 'github', url: url, - generatedEntities: await this.generateEntityDefinitions({ - repo: url, - }), + generatedEntities: analyzation.generateEntities.map((x: any) => x.entity), }; } @@ -174,9 +168,9 @@ the component will become available.\n\nFor more information, read an \ } // TODO: this could be part of the catalog api - private async generateEntityDefinitions(options: { + private async analyzeLocation(options: { repo: string; - }): Promise { + }): Promise { const { token } = await this.identityApi.getCredentials(); const response = await fetch( `${await this.discoveryApi.getBaseUrl('catalog')}/analyze-location`, @@ -200,69 +194,7 @@ the component will become available.\n\nFor more information, read an \ } const payload = await response.json(); - return payload.generateEntities.map((x: any) => x.entity); - } - - // TODO: this response should better be part of the analyze-locations response and scm-independent / implemented per scm - private async checkGitHubForExistingCatalogInfo(options: { - url: string; - owner: string; - repo: string; - githubIntegrationConfig: GitHubIntegrationConfig; - }): Promise< - Array<{ - target: string; - entities: CompoundEntityRef[]; - }> - > { - const { url, owner, repo, githubIntegrationConfig } = options; - - const { token } = await this.scmAuthApi.getCredentials({ url }); - const octo = new Octokit({ - auth: token, - baseUrl: githubIntegrationConfig.apiBaseUrl, - }); - const catalogFilename = getCatalogFilename(this.configApi); - const query = `repo:${owner}/${repo}+filename:${catalogFilename}`; - - const searchResult = await octo.search.code({ q: query }).catch(e => { - throw new Error( - formatHttpErrorMessage( - "Couldn't search repository for metadata file.", - e, - ), - ); - }); - const exists = searchResult.data.total_count > 0; - if (exists) { - const repoInformation = await octo.repos.get({ owner, repo }).catch(e => { - throw new Error(formatHttpErrorMessage("Couldn't fetch repo data", e)); - }); - const defaultBranch = repoInformation.data.default_branch; - - return await Promise.all( - searchResult.data.items - .map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`) - .map(async target => { - const result = await this.catalogApi.addLocation({ - type: 'url', - target, - dryRun: true, - }); - return { - target, - exists: result.exists, - entities: result.entities.map(e => ({ - kind: e.kind, - namespace: e.metadata.namespace ?? 'default', - name: e.metadata.name, - })), - }; - }), - ); - } - - return []; + return payload; } // TODO: extract this function and implement for non-github diff --git a/yarn.lock b/yarn.lock index 1178b3d940..fbb8955c8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4659,6 +4659,7 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" @@ -4669,6 +4670,7 @@ __metadata: "@backstage/plugin-search-backend-node": "workspace:^" "@backstage/plugin-search-common": "workspace:^" "@backstage/types": "workspace:^" + "@octokit/rest": ^19.0.4 "@types/core-js": ^2.5.4 "@types/express": ^4.17.6 "@types/git-url-parse": ^9.0.0 @@ -4796,6 +4798,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/integration-react": "workspace:^" + "@backstage/plugin-catalog-backend": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@material-ui/core": ^4.12.2 @@ -11576,7 +11579,7 @@ __metadata: languageName: node linkType: hard -"@octokit/rest@npm:^19.0.3": +"@octokit/rest@npm:^19.0.3, @octokit/rest@npm:^19.0.4": version: 19.0.4 resolution: "@octokit/rest@npm:19.0.4" dependencies: From 272d95bd70993ce2db9f13c7a51ce0e0d8f10295 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 22 Sep 2022 00:20:25 +0200 Subject: [PATCH 02/35] more useful name Signed-off-by: Kiss Miklos --- .../catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts | 4 ++-- plugins/catalog-backend/src/ingestion/types.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts index b96b50649e..885544bfd5 100644 --- a/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts @@ -19,7 +19,7 @@ import { GitHubIntegration } from '@backstage/integration'; import { DiscoveryApi } from '@backstage/plugin-permission-common'; import { Octokit } from '@octokit/rest'; import { trimEnd } from 'lodash'; -import { AnalyzeLocationExistingEntity, BaseLocationAnalyzer } from './types'; +import { AnalyzeLocationExistingEntity, ScmLocationAnalyzer } from './types'; export type GitHubLocationAnalyzerOptions = { integration: GitHubIntegration; @@ -27,7 +27,7 @@ export type GitHubLocationAnalyzerOptions = { discovery: DiscoveryApi; }; -export class GitHubLocationAnalyzer implements BaseLocationAnalyzer { +export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { private readonly integration: GitHubIntegration; private readonly catalogFilename: string; private readonly discovery: DiscoveryApi; diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 149795d166..f7b1760441 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -99,7 +99,7 @@ export type AnalyzeLocationEntityField = { description: string; }; -export interface BaseLocationAnalyzer { +export interface ScmLocationAnalyzer { analyze( owner: string, repo: string, From af7fffa3a40c3922a26a3eee4405a48ae943d902 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 22 Sep 2022 01:07:23 +0200 Subject: [PATCH 03/35] api-report Signed-off-by: Kiss Miklos --- plugins/catalog-backend/api-report.md | 9 ++++++--- plugins/catalog-backend/src/service/CatalogPlugin.ts | 4 ++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index f18d396114..d3abca1c94 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -18,10 +18,12 @@ 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 { CompoundEntityRef } from '@backstage/catalog-model'; 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 { DiscoveryApi } from '@backstage/core-plugin-api'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { Entity } from '@backstage/catalog-model'; import { EntityPolicy } from '@backstage/catalog-model'; @@ -64,9 +66,9 @@ export type AnalyzeLocationEntityField = { // @public export type AnalyzeLocationExistingEntity = { - location: LocationSpec; - isRegistered: boolean; - entity: Entity; + target: string; + exists: boolean | undefined; + entities: CompoundEntityRef[]; }; // @public @@ -218,6 +220,7 @@ export type CatalogEnvironment = { config: Config; reader: UrlReader; permissions: PermissionEvaluator | PermissionAuthorizer; + discovery: DiscoveryApi; }; // @alpha diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index 64e1e52494..3619460c4d 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -22,6 +22,7 @@ import { permissionsServiceRef, urlReaderServiceRef, httpRouterServiceRef, + discoveryServiceRef, } from '@backstage/backend-plugin-api'; import { CatalogBuilder } from './CatalogBuilder'; import { @@ -78,6 +79,7 @@ export const catalogPlugin = createBackendPlugin({ permissions: permissionsServiceRef, database: databaseServiceRef, httpRouter: httpRouterServiceRef, + discovery: discoveryServiceRef, }, async init({ logger, @@ -86,6 +88,7 @@ export const catalogPlugin = createBackendPlugin({ database, permissions, httpRouter, + discovery, }) { const winstonLogger = loggerToWinstonLogger(logger); const builder = await CatalogBuilder.create({ @@ -94,6 +97,7 @@ export const catalogPlugin = createBackendPlugin({ permissions, database, logger: winstonLogger, + discovery, }); builder.addProcessor(...processingExtensions.processors); builder.addEntityProvider(...processingExtensions.entityProviders); From b2e6cb6acfb4e5c452ee89704b61ae0d63c4eb2c Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Sat, 24 Sep 2022 17:59:57 +0200 Subject: [PATCH 04/35] add changeset Signed-off-by: Kiss Miklos --- .changeset/curvy-pets-wash.md | 5 +++++ .changeset/fuzzy-dolls-shake.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/curvy-pets-wash.md create mode 100644 .changeset/fuzzy-dolls-shake.md diff --git a/.changeset/curvy-pets-wash.md b/.changeset/curvy-pets-wash.md new file mode 100644 index 0000000000..7e43228fbc --- /dev/null +++ b/.changeset/curvy-pets-wash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials diff --git a/.changeset/fuzzy-dolls-shake.md b/.changeset/fuzzy-dolls-shake.md new file mode 100644 index 0000000000..92380b37ec --- /dev/null +++ b/.changeset/fuzzy-dolls-shake.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': minor +--- + +Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials From c0833d0d3f319602b975ca5d08ff14180b27c8dc Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 26 Sep 2022 12:19:15 +0200 Subject: [PATCH 05/35] use the correct type Signed-off-by: Kiss Miklos --- plugins/catalog-backend/api-report.md | 3 +-- plugins/catalog-backend/package.json | 1 - plugins/catalog-backend/src/service/CatalogBuilder.ts | 9 ++++++--- yarn.lock | 1 - 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index d3abca1c94..e1aec05e33 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -23,7 +23,6 @@ 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 { DiscoveryApi } from '@backstage/core-plugin-api'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { Entity } from '@backstage/catalog-model'; import { EntityPolicy } from '@backstage/catalog-model'; @@ -220,7 +219,7 @@ export type CatalogEnvironment = { config: Config; reader: UrlReader; permissions: PermissionEvaluator | PermissionAuthorizer; - discovery: DiscoveryApi; + discovery: PluginEndpointDiscovery; }; // @alpha diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 1e2d732f35..8232f1d05a 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -38,7 +38,6 @@ "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", - "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 2e20b0d4c3..f1d29f015c 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { PluginDatabaseManager, UrlReader } from '@backstage/backend-common'; +import { + PluginDatabaseManager, + PluginEndpointDiscovery, + UrlReader, +} from '@backstage/backend-common'; import { DefaultNamespaceEntityPolicy, Entity, @@ -96,7 +100,6 @@ import { RESOURCE_TYPE_CATALOG_ENTITY, } from '@backstage/plugin-catalog-common'; import { AuthorizedLocationService } from './AuthorizedLocationService'; -import { DiscoveryApi } from '@backstage/core-plugin-api'; /** @public */ export type CatalogEnvironment = { @@ -105,7 +108,7 @@ export type CatalogEnvironment = { config: Config; reader: UrlReader; permissions: PermissionEvaluator | PermissionAuthorizer; - discovery: DiscoveryApi; + discovery: PluginEndpointDiscovery; }; /** diff --git a/yarn.lock b/yarn.lock index fbb8955c8f..c3155dbfee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4659,7 +4659,6 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" - "@backstage/core-plugin-api": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" From a08b7939bb4f6fb436ea2e6e09b15da3ec340bce Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 26 Sep 2022 12:53:47 +0200 Subject: [PATCH 06/35] use the correct discovery type Signed-off-by: Kiss Miklos --- plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index c7354a40bc..1fbb6a3d47 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -26,18 +26,18 @@ import { AnalyzeLocationResponse, LocationAnalyzer, } from './types'; -import { DiscoveryApi } from '@backstage/plugin-permission-common'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { GitHubLocationAnalyzer } from './GitHubLocationAnalyzer'; export class RepoLocationAnalyzer implements LocationAnalyzer { private readonly logger: Logger; private readonly scmIntegrations: ScmIntegrationRegistry; - private readonly discovery: DiscoveryApi; + private readonly discovery: PluginEndpointDiscovery; constructor( logger: Logger, scmIntegrations: ScmIntegrationRegistry, - discovery: DiscoveryApi, + discovery: PluginEndpointDiscovery, ) { this.logger = logger; this.scmIntegrations = scmIntegrations; @@ -82,6 +82,7 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { default: break; } + if (analyzer) { const existingEntityFiles = await analyzer.analyze( owner, From 73db439b1d799717e4f3e70e6cbeb9e68f1227ee Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 26 Sep 2022 14:08:29 +0200 Subject: [PATCH 07/35] do not change public facing api Signed-off-by: Kiss Miklos --- plugins/catalog-backend/api-report.md | 7 ++--- .../src/ingestion/GitHubLocationAnalyzer.ts | 21 ++++++-------- .../catalog-backend/src/ingestion/types.ts | 8 ++--- .../src/api/CatalogImportClient.ts | 29 ++++++++++++++++++- 4 files changed, 44 insertions(+), 21 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index e1aec05e33..5745588873 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -18,7 +18,6 @@ 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 { CompoundEntityRef } from '@backstage/catalog-model'; import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common'; import { Conditions } from '@backstage/plugin-permission-node'; import { Config } from '@backstage/config'; @@ -65,9 +64,9 @@ export type AnalyzeLocationEntityField = { // @public export type AnalyzeLocationExistingEntity = { - target: string; - exists: boolean | undefined; - entities: CompoundEntityRef[]; + location: LocationSpec; + isRegistered: boolean; + entity: Entity; }; // @public diff --git a/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts index 885544bfd5..46fa87cd92 100644 --- a/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts @@ -24,7 +24,6 @@ import { AnalyzeLocationExistingEntity, ScmLocationAnalyzer } from './types'; export type GitHubLocationAnalyzerOptions = { integration: GitHubIntegration; catalogFilename?: string; - discovery: DiscoveryApi; }; export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { @@ -62,26 +61,24 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { }); const defaultBranch = repoInformation.data.default_branch; - return await Promise.all( + const result = await Promise.all( searchResult.data.items .map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`) .map(async target => { - const result = await catalogClient.addLocation({ + const addLocationResult = await catalogClient.addLocation({ type: 'url', target, dryRun: true, }); - return { - target, - exists: result.exists, - entities: result.entities.map(e => ({ - kind: e.kind, - namespace: e.metadata.namespace ?? 'default', - name: e.metadata.name, - })), - }; + return addLocationResult.entities.map(e => ({ + location: { type: 'url', target }, + isRegistered: !!addLocationResult.exists, + entity: e, + })); }), ); + + return result.flat(); } return []; } diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index f7b1760441..9da7b037aa 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { RecursivePartial } from '../util/RecursivePartial'; import { LocationSpec } from '@backstage/plugin-catalog-node'; @@ -50,9 +50,9 @@ export type AnalyzeLocationResponse = { * @public */ export type AnalyzeLocationExistingEntity = { - target: string; - exists: boolean | undefined; - entities: CompoundEntityRef[]; + location: LocationSpec; + isRegistered: boolean; + entity: Entity; }; /** diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index 0533b558c8..7aa3365488 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -31,6 +31,7 @@ import { AnalyzeResult, CatalogImportApi } from './CatalogImportApi'; import { getGithubIntegrationConfig } from './GitHub'; import { getBranchName, getCatalogFilename } from '../components/helpers'; import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend'; +import { CompoundEntityRef } from '@backstage/catalog-model'; /** * The default implementation of the {@link CatalogImportApi}. @@ -108,9 +109,35 @@ export class CatalogImportClient implements CatalogImportApi { }); if (analyzation.existingEntityFiles.length > 0) { + const locations = analyzation.existingEntityFiles.reduce< + Record< + string, + { + target: string; + exists?: boolean; + entities: CompoundEntityRef[]; + } + > + >((state, curr) => { + state[curr.location.target] = { + target: curr.location.target, + exists: curr.isRegistered, + entities: [ + ...(curr.location.target in state + ? state[curr.location.target].entities + : []), + { + name: curr.entity.metadata.name, + namespace: curr.entity.metadata.namespace ?? 'default', + kind: curr.entity.kind, + }, + ], + }; + return state; + }, {}); return { type: 'locations', - locations: analyzation.existingEntityFiles, + locations: Object.values(locations), }; } From 421b620af34f9afbbc028cd6fee2bdde5bd29c76 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Tue, 27 Sep 2022 10:46:15 +0200 Subject: [PATCH 08/35] add tests Signed-off-by: Kiss Miklos --- .../src/ingestion/LocationAnalyzer.ts | 4 +- .../analyzers/GitHubLocationAnalyzer.test.ts | 126 ++++++++++++++++++ .../{ => analyzers}/GitHubLocationAnalyzer.ts | 47 +++---- 3 files changed, 151 insertions(+), 26 deletions(-) create mode 100644 plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts rename plugins/catalog-backend/src/ingestion/{ => analyzers}/GitHubLocationAnalyzer.ts (67%) diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index 1fbb6a3d47..efd6a05fcf 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -27,7 +27,7 @@ import { LocationAnalyzer, } from './types'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { GitHubLocationAnalyzer } from './GitHubLocationAnalyzer'; +import { GitHubLocationAnalyzer } from './analyzers/GitHubLocationAnalyzer'; export class RepoLocationAnalyzer implements LocationAnalyzer { private readonly logger: Logger; @@ -85,8 +85,6 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { if (analyzer) { const existingEntityFiles = await analyzer.analyze( - owner, - name, request.location.target, ); if (existingEntityFiles.length > 0) { diff --git a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts new file mode 100644 index 0000000000..11bb3137c7 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts @@ -0,0 +1,126 @@ +/* + * 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. + */ + +const octokit = { + search: { + code: jest.fn(), + }, + repos: { + get: jest.fn(), + }, +}; + +jest.mock('@octokit/rest', () => { + class Octokit { + constructor() { + return octokit; + } + } + return { Octokit }; +}); + +import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { GitHubLocationAnalyzer } from './GitHubLocationAnalyzer'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { GitHubIntegration } from '@backstage/integration'; + +const server = setupServer(); + +describe('GitHubLocationAnalyzer', () => { + const mockDiscoveryApi: jest.Mocked = { + getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7007'), + getExternalBaseUrl: jest.fn(), + }; + const integration = new GitHubIntegration({ + host: 'h.com', + apiBaseUrl: 'a', + rawBaseUrl: 'r', + token: 't', + }); + + setupRequestMockHandlers(server); + + beforeEach(() => { + server.use( + rest.post('http://localhost:7007/locations', async (req, res, ctx) => { + return res( + ctx.status(201), + ctx.json({ + location: 'test', + exists: false, + entities: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + name: 'test-entity', + }, + spec: { + type: 'url', + target: 'whatever', + }, + }, + { + 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', + }, + }, + ], + }), + ); + }), + ); + }); + + it('should analyze', async () => { + octokit.search.code.mockImplementation((opts: { q: string }) => { + if (opts.q === 'filename:catalog-info.yaml repo:foo/bar') { + return Promise.resolve({ + data: { items: [{ path: 'catalog-info.yaml' }], total_count: 1 }, + }); + } + return Promise.reject(); + }); + + octokit.repos.get.mockResolvedValue({ + data: { default_branch: 'my_default_branch' }, + }); + + const analyzer = new GitHubLocationAnalyzer({ + discovery: mockDiscoveryApi, + integration, + }); + const result = await analyzer.analyze('https://github.com/foo/bar'); + + expect(result[0].isRegistered).toBeFalsy(); + expect(result[0].location).toEqual({ + type: 'url', + target: + 'https://github.com/foo/bar/blob/my_default_branch/catalog-info.yaml', + }); + }); +}); diff --git a/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.ts similarity index 67% rename from plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts rename to plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.ts index 46fa87cd92..2e42f202f9 100644 --- a/plugins/catalog-backend/src/ingestion/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import { CatalogClient } from '@backstage/catalog-client'; +import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; import { GitHubIntegration } from '@backstage/integration'; import { DiscoveryApi } from '@backstage/plugin-permission-common'; import { Octokit } from '@octokit/rest'; import { trimEnd } from 'lodash'; -import { AnalyzeLocationExistingEntity, ScmLocationAnalyzer } from './types'; +import parseGitUrl from 'git-url-parse'; +import { AnalyzeLocationExistingEntity, ScmLocationAnalyzer } from '../types'; export type GitHubLocationAnalyzerOptions = { integration: GitHubIntegration; @@ -27,45 +28,45 @@ export type GitHubLocationAnalyzerOptions = { discovery: DiscoveryApi; }; export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { - private readonly integration: GitHubIntegration; private readonly catalogFilename: string; private readonly discovery: DiscoveryApi; + private readonly octokitClient: Octokit; + private readonly catalogClient: CatalogApi; constructor(options: GitHubLocationAnalyzerOptions) { - this.integration = options.integration; this.catalogFilename = options.catalogFilename || 'catalog-info.yaml'; this.discovery = options.discovery; + this.octokitClient = new Octokit({ + auth: options.integration.config.token, + baseUrl: options.integration.config.apiBaseUrl, + }); + this.catalogClient = new CatalogClient({ discoveryApi: this.discovery }); } - async analyze( - owner: string, - repo: string, - url: string, - ): Promise { - const octo = new Octokit({ - auth: this.integration.config.token, - baseUrl: this.integration.config.apiBaseUrl, - }); - const query = `filename:${this.catalogFilename} repo:${owner}/${repo} `; + async analyze(url: string): Promise { + const { owner, name: repo } = parseGitUrl(url); + const query = `filename:${this.catalogFilename} repo:${owner}/${repo}`; - const catalogClient = new CatalogClient({ discoveryApi: this.discovery }); - - const searchResult = await octo.search.code({ q: query }).catch(e => { - throw new Error(`Couldn't search repository for metadata file, ${e}`); - }); + const searchResult = await this.octokitClient.search + .code({ q: query }) + .catch(e => { + throw new Error(`Couldn't search repository for metadata file, ${e}`); + }); const exists = searchResult.data.total_count > 0; if (exists) { - const repoInformation = await octo.repos.get({ owner, repo }).catch(e => { - throw new Error(`Couldn't fetch repo data, ${e}`); - }); + const repoInformation = await this.octokitClient.repos + .get({ owner, repo }) + .catch(e => { + throw new Error(`Couldn't fetch repo data, ${e}`); + }); const defaultBranch = repoInformation.data.default_branch; const result = await Promise.all( searchResult.data.items .map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`) .map(async target => { - const addLocationResult = await catalogClient.addLocation({ + const addLocationResult = await this.catalogClient.addLocation({ type: 'url', target, dryRun: true, From 48117acb8cc65825931b2473f5d1b3f1915c16b3 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Tue, 27 Sep 2022 15:49:01 +0200 Subject: [PATCH 09/35] refactor Signed-off-by: Kiss Miklos --- .../src/ingestion/LocationAnalyzer.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index efd6a05fcf..f1fede65df 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -51,15 +51,6 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { ) as GitHubIntegration; const { owner, name } = parseGitUrl(request.location.target); - const entity: Entity = { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Component', - metadata: { - name: name, - }, - spec: { type: 'other', lifecycle: 'unknown' }, - }; - let annotationPrefix; let analyzer; switch (integration?.type) { @@ -98,6 +89,15 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { } } + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: name, + }, + spec: { type: 'other', lifecycle: 'unknown' }, + }; + if (annotationPrefix) { entity.metadata.annotations = { [`${annotationPrefix}/project-slug`]: `${owner}/${name}`, From fe99c5d41466d814f43b805093265205f12f85d1 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Tue, 27 Sep 2022 15:56:04 +0200 Subject: [PATCH 10/35] fox tsc Signed-off-by: Kiss Miklos --- .../src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts index 11bb3137c7..7f02fee620 100644 --- a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts @@ -57,7 +57,7 @@ describe('GitHubLocationAnalyzer', () => { beforeEach(() => { server.use( - rest.post('http://localhost:7007/locations', async (req, res, ctx) => { + rest.post('http://localhost:7007/locations', async (_, res, ctx) => { return res( ctx.status(201), ctx.json({ @@ -123,4 +123,5 @@ describe('GitHubLocationAnalyzer', () => { 'https://github.com/foo/bar/blob/my_default_branch/catalog-info.yaml', }); }); + it('should'); }); From 11f8f63b0705d0c079938961ceba6442f4146441 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Tue, 27 Sep 2022 19:21:21 +0200 Subject: [PATCH 11/35] test optional entity filename for search Signed-off-by: Kiss Miklos --- plugins/catalog-backend/api-report.md | 1 + .../src/ingestion/LocationAnalyzer.ts | 1 + .../analyzers/GitHubLocationAnalyzer.test.ts | 31 +++- .../catalog-backend/src/ingestion/types.ts | 1 + .../src/service/createRouter.ts | 10 +- .../src/api/CatalogImportClient.test.ts | 162 +++++++++++++----- .../src/api/CatalogImportClient.ts | 7 + 7 files changed, 164 insertions(+), 49 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 5745588873..bf53badd87 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -78,6 +78,7 @@ export type AnalyzeLocationGenerateEntity = { // @public (undocumented) export type AnalyzeLocationRequest = { location: LocationSpec; + catalogFilename?: string; }; // @public (undocumented) diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index f1fede65df..24c5821d92 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -65,6 +65,7 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { analyzer = new GitHubLocationAnalyzer({ integration, discovery: this.discovery, + catalogFilename: request.catalogFilename, }); break; case 'gitlab': diff --git a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts index 7f02fee620..fbc9ae3454 100644 --- a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts @@ -94,6 +94,10 @@ describe('GitHubLocationAnalyzer', () => { ); }), ); + + octokit.repos.get.mockResolvedValue({ + data: { default_branch: 'my_default_branch' }, + }); }); it('should analyze', async () => { @@ -106,10 +110,6 @@ describe('GitHubLocationAnalyzer', () => { return Promise.reject(); }); - octokit.repos.get.mockResolvedValue({ - data: { default_branch: 'my_default_branch' }, - }); - const analyzer = new GitHubLocationAnalyzer({ discovery: mockDiscoveryApi, integration, @@ -123,5 +123,26 @@ describe('GitHubLocationAnalyzer', () => { 'https://github.com/foo/bar/blob/my_default_branch/catalog-info.yaml', }); }); - it('should'); + it('should use the provided entity filename for search', async () => { + octokit.search.code.mockImplementation((opts: { q: string }) => { + if (opts.q === 'filename:anvil.yaml repo:foo/bar') { + return Promise.resolve({ + data: { items: [{ path: 'anvil.yaml' }], total_count: 1 }, + }); + } + return Promise.reject(); + }); + + const analyzer = new GitHubLocationAnalyzer({ + discovery: mockDiscoveryApi, + integration, + catalogFilename: 'anvil.yaml', + }); + const result = await analyzer.analyze('https://github.com/foo/bar'); + + expect(result[0].location).toEqual({ + type: 'url', + target: 'https://github.com/foo/bar/blob/my_default_branch/anvil.yaml', + }); + }); }); diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 9da7b037aa..8dc833abd8 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -34,6 +34,7 @@ export type LocationAnalyzer = { /** @public */ export type AnalyzeLocationRequest = { location: LocationSpec; + catalogFilename?: string; }; /** @public */ diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index 639893dcc1..93a4e20726 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -229,9 +229,15 @@ export async function createRouter( router.post('/analyze-location', async (req, res) => { const body = await validateRequestBody( req, - z.object({ location: locationInput }), + z.object({ + location: locationInput, + catalogFilename: z.string().optional(), + }), ); - const schema = z.object({ location: locationInput }); + const schema = z.object({ + location: locationInput, + catalogFilename: z.string().optional(), + }); const output = await locationAnalyzer.analyzeLocation(schema.parse(body)); res.status(200).json(output); }); diff --git a/plugins/catalog-import/src/api/CatalogImportClient.test.ts b/plugins/catalog-import/src/api/CatalogImportClient.test.ts index 30b9f47d99..d507ac3bef 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.test.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.test.ts @@ -297,7 +297,7 @@ describe('CatalogImportClient', () => { it('should find locations from github', async () => { (new Octokit().search.code as any as jest.Mock).mockResolvedValueOnce({ data: { - total_count: 2, + total_count: 3, items: [ { path: 'simple/path/catalog-info.yaml' }, { path: 'co/mple/x/path/catalog-info.yaml' }, @@ -305,24 +305,72 @@ describe('CatalogImportClient', () => { ], }, }); - - catalogApi.addLocation.mockImplementation(async ({ type, target }) => ({ - location: { - id: 'id-0', - type: type ?? 'url', - target, - }, - entities: [ - { - apiVersion: '1', - kind: 'k', - metadata: { - name: 'e', - namespace: 'n', + server.use( + rest.post(`${mockBaseUrl}/analyze-location`, (req, res, ctx) => { + expect(req.body).toEqual({ + location: { + target: 'https://github.com/backstage/backstage', + type: 'url', }, - }, - ], - })); + }); + + return res( + ctx.json({ + generateEntities: [], + existingEntityFiles: [ + { + isRegistered: false, + location: { + type: 'url', + target: + 'https://github.com/backstage/backstage/blob/main/simple/path/catalog-info.yaml', + }, + entity: { + apiVersion: '1', + kind: 'k', + metadata: { + name: 'e', + namespace: 'n', + }, + }, + }, + { + isRegistered: false, + location: { + type: 'url', + target: + 'https://github.com/backstage/backstage/blob/main/co/mple/x/path/catalog-info.yaml', + }, + entity: { + apiVersion: '1', + kind: 'k', + metadata: { + name: 'e', + namespace: 'n', + }, + }, + }, + { + isRegistered: false, + location: { + type: 'url', + target: + 'https://github.com/backstage/backstage/blob/main/catalog-info.yaml', + }, + entity: { + apiVersion: '1', + kind: 'k', + metadata: { + name: 'e', + namespace: 'n', + }, + }, + }, + ], + }), + ); + }), + ); await expect( catalogImportClient.analyzeUrl( @@ -332,16 +380,19 @@ describe('CatalogImportClient', () => { locations: [ { entities: [{ kind: 'k', name: 'e', namespace: 'n' }], + exists: false, target: 'https://github.com/backstage/backstage/blob/main/simple/path/catalog-info.yaml', }, { entities: [{ kind: 'k', name: 'e', namespace: 'n' }], + exists: false, target: 'https://github.com/backstage/backstage/blob/main/co/mple/x/path/catalog-info.yaml', }, { entities: [{ kind: 'k', name: 'e', namespace: 'n' }], + exists: false, target: 'https://github.com/backstage/backstage/blob/main/catalog-info.yaml', }, @@ -426,31 +477,57 @@ describe('CatalogImportClient', () => { }), ); - catalogApi.addLocation.mockImplementation(async ({ type, target }) => ({ - location: { - id: 'id-0', - type: type ?? 'url', - target, - }, - entities: [ - { - apiVersion: '1', - kind: 'Location', - metadata: { - name: 'my-entity', - namespace: 'my-namespace', + server.use( + rest.post(`${mockBaseUrl}/analyze-location`, (req, res, ctx) => { + expect(req.body).toEqual({ + location: { + target: 'https://github.com/acme-corp/our-awesome-api', + type: 'url', }, - }, - { - apiVersion: '1', - kind: 'Component', - metadata: { - name: 'my-entity', - namespace: 'my-namespace', - }, - }, - ], - })); + catalogFilename: 'anvil.yaml', + }); + + return res( + ctx.json({ + generateEntities: [], + existingEntityFiles: [ + { + isRegistered: false, + location: { + type: 'url', + target: + 'https://github.com/acme-corp/our-awesome-api/blob/main/anvil.yaml', + }, + entity: { + apiVersion: '1', + kind: 'Location', + metadata: { + name: 'my-entity', + namespace: 'my-namespace', + }, + }, + }, + { + isRegistered: false, + location: { + type: 'url', + target: + 'https://github.com/acme-corp/our-awesome-api/blob/main/anvil.yaml', + }, + entity: { + apiVersion: '1', + kind: 'Component', + metadata: { + name: 'my-entity', + namespace: 'my-namespace', + }, + }, + }, + ], + }), + ); + }), + ); await expect( catalogImportClient.analyzeUrl(repositoryUrl), @@ -470,6 +547,7 @@ describe('CatalogImportClient', () => { }, ], target: `${repositoryUrl}/blob/main/${entityFilename}`, + exists: false, }, ], type: 'locations', diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index 7aa3365488..5dd3a3178f 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -209,6 +209,13 @@ the component will become available.\n\nFor more information, read an \ method: 'POST', body: JSON.stringify({ location: { type: 'url', target: options.repo }, + ...(this.configApi.getOptionalString( + 'catalog.import.entityFilename', + ) && { + catalogFilename: this.configApi.getOptionalString( + 'catalog.import.entityFilename', + ), + }), }), }, ).catch(e => { From 59ad2b9d259d04b96f001a8b4b5cb6e15e0ceef8 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 29 Sep 2022 19:28:26 +0200 Subject: [PATCH 12/35] move github deps to their own plugin Signed-off-by: Kiss Miklos --- .../package.json | 3 + .../analyzers/GitHubLocationAnalyzer.test.ts | 29 ++++++---- .../src}/analyzers/GitHubLocationAnalyzer.ts | 58 ++++++++++++------- .../src/index.ts | 1 + .../src/lib/github.ts | 33 +++++++++++ .../src/lib/index.ts | 1 + .../src/ingestion/LocationAnalyzer.ts | 24 ++++---- .../catalog-backend/src/ingestion/index.ts | 2 + .../catalog-backend/src/ingestion/types.ts | 18 +++--- .../src/service/CatalogBuilder.ts | 23 +++++++- yarn.lock | 1 + 11 files changed, 140 insertions(+), 53 deletions(-) rename plugins/{catalog-backend/src/ingestion => catalog-backend-module-github/src}/analyzers/GitHubLocationAnalyzer.test.ts (89%) rename plugins/{catalog-backend/src/ingestion => catalog-backend-module-github/src}/analyzers/GitHubLocationAnalyzer.ts (62%) diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 23d9cbecfc..950c7723e8 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -36,6 +36,7 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", + "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", @@ -44,6 +45,8 @@ "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", "@octokit/graphql": "^5.0.0", + "@octokit/rest": "^19.0.4", + "git-url-parse": "^13.1.0", "lodash": "^4.17.21", "msw": "^0.47.0", "node-fetch": "^2.6.7", diff --git a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts similarity index 89% rename from plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts rename to plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts index fbc9ae3454..5ceb9cf85c 100644 --- a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts @@ -46,12 +46,17 @@ describe('GitHubLocationAnalyzer', () => { getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7007'), getExternalBaseUrl: jest.fn(), }; - const integration = new GitHubIntegration({ - host: 'h.com', - apiBaseUrl: 'a', - rawBaseUrl: 'r', - token: 't', - }); + const integrations = { + list: jest.fn(), + byHost: jest.fn(), + byUrl: () => + new GitHubIntegration({ + host: 'h.com', + apiBaseUrl: 'a', + rawBaseUrl: 'r', + token: 't', + }), + }; setupRequestMockHandlers(server); @@ -112,9 +117,11 @@ describe('GitHubLocationAnalyzer', () => { const analyzer = new GitHubLocationAnalyzer({ discovery: mockDiscoveryApi, - integration, + integrations, + }); + const result = await analyzer.analyze({ + url: 'https://github.com/foo/bar', }); - const result = await analyzer.analyze('https://github.com/foo/bar'); expect(result[0].isRegistered).toBeFalsy(); expect(result[0].location).toEqual({ @@ -135,10 +142,12 @@ describe('GitHubLocationAnalyzer', () => { const analyzer = new GitHubLocationAnalyzer({ discovery: mockDiscoveryApi, - integration, + integrations, + }); + const result = await analyzer.analyze({ + url: 'https://github.com/foo/bar', catalogFilename: 'anvil.yaml', }); - const result = await analyzer.analyze('https://github.com/foo/bar'); expect(result[0].location).toEqual({ type: 'url', diff --git a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts similarity index 62% rename from plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.ts rename to plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts index 2e42f202f9..218d56410d 100644 --- a/plugins/catalog-backend/src/ingestion/analyzers/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts @@ -15,39 +15,57 @@ */ import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; -import { GitHubIntegration } from '@backstage/integration'; -import { DiscoveryApi } from '@backstage/plugin-permission-common'; +import { + GitHubIntegration, + ScmIntegrationsGroup, +} from '@backstage/integration'; import { Octokit } from '@octokit/rest'; import { trimEnd } from 'lodash'; import parseGitUrl from 'git-url-parse'; -import { AnalyzeLocationExistingEntity, ScmLocationAnalyzer } from '../types'; +import { + AnalyzeLocationExistingEntity, + AnalyzeOptions, + ScmLocationAnalyzer, +} from '@backstage/plugin-catalog-backend'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; export type GitHubLocationAnalyzerOptions = { - integration: GitHubIntegration; + integrations: ScmIntegrationsGroup; catalogFilename?: string; - discovery: DiscoveryApi; + discovery: PluginEndpointDiscovery; }; export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { - private readonly catalogFilename: string; - private readonly discovery: DiscoveryApi; - private readonly octokitClient: Octokit; private readonly catalogClient: CatalogApi; + private readonly integrations: ScmIntegrationsGroup; constructor(options: GitHubLocationAnalyzerOptions) { - this.catalogFilename = options.catalogFilename || 'catalog-info.yaml'; - this.discovery = options.discovery; - this.octokitClient = new Octokit({ - auth: options.integration.config.token, - baseUrl: options.integration.config.apiBaseUrl, - }); - this.catalogClient = new CatalogClient({ discoveryApi: this.discovery }); + this.integrations = options.integrations; + this.catalogClient = new CatalogClient({ discoveryApi: options.discovery }); } - - async analyze(url: string): Promise { + getIntegrationType() { + return 'github'; + } + async analyze({ + url, + catalogFilename, + }: AnalyzeOptions): Promise { const { owner, name: repo } = parseGitUrl(url); - const query = `filename:${this.catalogFilename} repo:${owner}/${repo}`; - const searchResult = await this.octokitClient.search + const catalogFile = catalogFilename || 'catalog-info.yaml'; + + const query = `filename:${catalogFile} repo:${owner}/${repo}`; + + const integration = this.integrations.byUrl(url); + if (!integration) { + throw new Error('Make sure you have a GitHub integration configured'); + } + + const octokitClient = new Octokit({ + auth: integration.config.token, + baseUrl: integration.config.apiBaseUrl, + }); + + const searchResult = await octokitClient.search .code({ q: query }) .catch(e => { throw new Error(`Couldn't search repository for metadata file, ${e}`); @@ -55,7 +73,7 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { const exists = searchResult.data.total_count > 0; if (exists) { - const repoInformation = await this.octokitClient.repos + const repoInformation = await octokitClient.repos .get({ owner, repo }) .catch(e => { throw new Error(`Couldn't fetch repo data, ${e}`); diff --git a/plugins/catalog-backend-module-github/src/index.ts b/plugins/catalog-backend-module-github/src/index.ts index d793d48f1e..e856277873 100644 --- a/plugins/catalog-backend-module-github/src/index.ts +++ b/plugins/catalog-backend-module-github/src/index.ts @@ -29,3 +29,4 @@ export type { GitHubOrgEntityProviderOptions } from './providers/GitHubOrgEntity export type { GithubMultiOrgConfig } from './lib'; export { githubEntityProviderCatalogModule } from './module'; export type { GithubEntityProviderCatalogModuleOptions } from './module'; +export { GitHubLocationAnalyzer } from './analyzers/GitHubLocationAnalyzer'; diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index 6a944af330..ec904a52e0 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -308,6 +308,39 @@ export async function getOrganizationRepositories( return { repositories }; } +export async function getRepository( + client: typeof graphql, + org: string, + name: string, +): Promise { + const query = ` + query repositories($org: String!, $name: String!) { + repository(name: $name, owner: $org) { + name + url + isArchived + repositoryTopics(first: 100) { + nodes { + ... on RepositoryTopic { + topic { + name + } + } + } + } + defaultBranchRef { + name + } + } + }`; + + const repository: Repository = await client(query, { + org, + name, + }); + return repository; +} + /** * Gets all the users out of a GitHub organization. * diff --git a/plugins/catalog-backend-module-github/src/lib/index.ts b/plugins/catalog-backend-module-github/src/lib/index.ts index 26ec3e8516..28cbe898d6 100644 --- a/plugins/catalog-backend-module-github/src/lib/index.ts +++ b/plugins/catalog-backend-module-github/src/lib/index.ts @@ -20,6 +20,7 @@ export { getOrganizationRepositories, getOrganizationTeams, getOrganizationUsers, + getRepository, } from './github'; export { assignGroupsToUsers, buildOrgHierarchy } from './org'; export { parseGitHubOrgUrl } from './util'; diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index 24c5821d92..7992903b65 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -25,23 +25,22 @@ import { AnalyzeLocationRequest, AnalyzeLocationResponse, LocationAnalyzer, + ScmLocationAnalyzer, } from './types'; -import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { GitHubLocationAnalyzer } from './analyzers/GitHubLocationAnalyzer'; export class RepoLocationAnalyzer implements LocationAnalyzer { private readonly logger: Logger; private readonly scmIntegrations: ScmIntegrationRegistry; - private readonly discovery: PluginEndpointDiscovery; + private readonly analyzers: ScmLocationAnalyzer[]; constructor( logger: Logger, scmIntegrations: ScmIntegrationRegistry, - discovery: PluginEndpointDiscovery, + analyzers: ScmLocationAnalyzer[], ) { this.logger = logger; this.scmIntegrations = scmIntegrations; - this.discovery = discovery; + this.analyzers = analyzers; } async analyzeLocation( request: AnalyzeLocationRequest, @@ -52,7 +51,6 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { const { owner, name } = parseGitUrl(request.location.target); let annotationPrefix; - let analyzer; switch (integration?.type) { case 'azure': annotationPrefix = 'dev.azure.com'; @@ -62,11 +60,6 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { break; case 'github': annotationPrefix = 'github.com'; - analyzer = new GitHubLocationAnalyzer({ - integration, - discovery: this.discovery, - catalogFilename: request.catalogFilename, - }); break; case 'gitlab': annotationPrefix = 'gitlab.com'; @@ -75,10 +68,13 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { break; } + const analyzer = this.analyzers.find( + a => a.getIntegrationType() === integration.type, + ); if (analyzer) { - const existingEntityFiles = await analyzer.analyze( - request.location.target, - ); + const existingEntityFiles = await analyzer.analyze({ + url: request.location.target, + }); if (existingEntityFiles.length > 0) { this.logger.debug( `entity for ${request.location.target} already exists.`, diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts index c97d029b5c..0b00809043 100644 --- a/plugins/catalog-backend/src/ingestion/index.ts +++ b/plugins/catalog-backend/src/ingestion/index.ts @@ -21,4 +21,6 @@ export type { AnalyzeLocationRequest, AnalyzeLocationResponse, LocationAnalyzer, + ScmLocationAnalyzer, + AnalyzeOptions, } from './types'; diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 8dc833abd8..961be78572 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -100,10 +100,14 @@ export type AnalyzeLocationEntityField = { description: string; }; -export interface ScmLocationAnalyzer { - analyze( - owner: string, - repo: string, - url: string, - ): Promise; -} +export type AnalyzeOptions = { + url: string; + catalogFilename?: string; +}; +/** @public */ +export type ScmLocationAnalyzer = { + /** The integration type this location analyzer can work with */ + getIntegrationType(): string; + /** This function is responsible to figure out if the catalog file is already present in the repository */ + analyze(options: AnalyzeOptions): Promise; +}; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index f1d29f015c..97379ac491 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -60,7 +60,7 @@ import { yamlPlaceholderResolver, } from '../modules/core/PlaceholderProcessor'; import { defaultEntityDataParser } from '../modules/util/parse'; -import { LocationAnalyzer } from '../ingestion/types'; +import { LocationAnalyzer, ScmLocationAnalyzer } from '../ingestion/types'; import { CatalogProcessingEngine } from '../processing'; import { DefaultProcessingDatabase } from '../database/DefaultProcessingDatabase'; import { applyDatabaseMigrations } from '../database/migrations'; @@ -120,6 +120,9 @@ export type CatalogEnvironment = { * after the processors' pre-processing steps. All policies are given the * chance to inspect the entity, and all of them have to pass in order for * the entity to be considered valid from an overall point of view. + * - Location analyzers can be added. These are responsible to analyze the + * the existence of a catalog-info.yaml file int he provided git repository + * when you use the /catalog-import page with a repository url. * - Placeholder resolvers can be replaced or added. These run on the raw * structured data between the parsing and pre-processing steps, to replace * dollar-prefixed entries with their actual values (like $file). @@ -140,6 +143,7 @@ export class CatalogBuilder { private fieldFormatValidators: Partial; private entityProviders: EntityProvider[]; private processors: CatalogProcessor[]; + private locationAnalyzers: ScmLocationAnalyzer[]; private processorsReplace: boolean; private parser: CatalogProcessorParser | undefined; private onProcessingError?: (event: { @@ -170,6 +174,7 @@ export class CatalogBuilder { this.fieldFormatValidators = {}; this.entityProviders = []; this.processors = []; + this.locationAnalyzers = []; this.processorsReplace = false; this.parser = undefined; this.permissionRules = Object.values(catalogPermissionRules); @@ -340,6 +345,20 @@ export class CatalogBuilder { ]; } + /** + * Adds Location Analyzers. These are responsible for figuring out + * if the repository already contains a catalog-info.yaml file when + * you register a repostiroy in the /catalog-import page + * + * @param locationAnalyzers - One or more location analyzers + */ + addLocationAnalyzers( + ...analyzers: Array> + ): CatalogBuilder { + this.locationAnalyzers.push(...analyzers.flat()); + return this; + } + /** * Sets up the catalog to use a custom parser for entity data. * @@ -484,7 +503,7 @@ export class CatalogBuilder { const locationAnalyzer = this.locationAnalyzer ?? - new RepoLocationAnalyzer(logger, integrations, this.env.discovery); + new RepoLocationAnalyzer(logger, integrations, this.locationAnalyzers); const locationService = new AuthorizedLocationService( new DefaultLocationService(locationStore, orchestrator, { allowedLocationTypes: this.allowedLocationType, diff --git a/yarn.lock b/yarn.lock index c3155dbfee..718736394b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4550,6 +4550,7 @@ __metadata: "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" "@octokit/graphql": ^5.0.0 + "@octokit/rest": ^19.0.4 "@types/lodash": ^4.14.151 lodash: ^4.17.21 msw: ^0.47.0 From 7022aebf35b0c3def2b84593b48c6c6290fd79b7 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 29 Sep 2022 19:32:42 +0200 Subject: [PATCH 13/35] add changeset Signed-off-by: Kiss Miklos --- .changeset/moody-carrots-shout.md | 5 +++ .../src/lib/github.ts | 33 ------------------- yarn.lock | 4 ++- 3 files changed, 8 insertions(+), 34 deletions(-) create mode 100644 .changeset/moody-carrots-shout.md diff --git a/.changeset/moody-carrots-shout.md b/.changeset/moody-carrots-shout.md new file mode 100644 index 0000000000..a1c357041f --- /dev/null +++ b/.changeset/moody-carrots-shout.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': minor +--- + +Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given url that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index ec904a52e0..6a944af330 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -308,39 +308,6 @@ export async function getOrganizationRepositories( return { repositories }; } -export async function getRepository( - client: typeof graphql, - org: string, - name: string, -): Promise { - const query = ` - query repositories($org: String!, $name: String!) { - repository(name: $name, owner: $org) { - name - url - isArchived - repositoryTopics(first: 100) { - nodes { - ... on RepositoryTopic { - topic { - name - } - } - } - } - defaultBranchRef { - name - } - } - }`; - - const repository: Repository = await client(query, { - org, - name, - }); - return repository; -} - /** * Gets all the users out of a GitHub organization. * diff --git a/yarn.lock b/yarn.lock index 718736394b..a418c7e297 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4541,6 +4541,7 @@ __metadata: "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" + "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -4552,6 +4553,7 @@ __metadata: "@octokit/graphql": ^5.0.0 "@octokit/rest": ^19.0.4 "@types/lodash": ^4.14.151 + git-url-parse: ^13.1.0 lodash: ^4.17.21 msw: ^0.47.0 node-fetch: ^2.6.7 @@ -23512,7 +23514,7 @@ __metadata: languageName: node linkType: hard -"git-url-parse@npm:^13.0.0": +"git-url-parse@npm:^13.0.0, git-url-parse@npm:^13.1.0": version: 13.1.0 resolution: "git-url-parse@npm:13.1.0" dependencies: From 2f5b97df6c30087e0ba353424ea7b0749d6f3b1d Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 29 Sep 2022 19:38:51 +0200 Subject: [PATCH 14/35] get correct integration from config Signed-off-by: Kiss Miklos --- .../src/analyzers/GitHubLocationAnalyzer.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts index 218d56410d..7ab92565ce 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts @@ -15,10 +15,7 @@ */ import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; -import { - GitHubIntegration, - ScmIntegrationsGroup, -} from '@backstage/integration'; +import { ScmIntegrations } from '@backstage/integration'; import { Octokit } from '@octokit/rest'; import { trimEnd } from 'lodash'; import parseGitUrl from 'git-url-parse'; @@ -28,18 +25,18 @@ import { ScmLocationAnalyzer, } from '@backstage/plugin-catalog-backend'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { Config } from '@backstage/config'; export type GitHubLocationAnalyzerOptions = { - integrations: ScmIntegrationsGroup; - catalogFilename?: string; + config: Config; discovery: PluginEndpointDiscovery; }; export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { private readonly catalogClient: CatalogApi; - private readonly integrations: ScmIntegrationsGroup; + private readonly config: Config; constructor(options: GitHubLocationAnalyzerOptions) { - this.integrations = options.integrations; + this.config = options.config; this.catalogClient = new CatalogClient({ discoveryApi: options.discovery }); } getIntegrationType() { @@ -55,7 +52,9 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { const query = `filename:${catalogFile} repo:${owner}/${repo}`; - const integration = this.integrations.byUrl(url); + const integration = ScmIntegrations.fromConfig(this.config).github.byUrl( + url, + ); if (!integration) { throw new Error('Make sure you have a GitHub integration configured'); } From 3cbe42d8a97a331444286c6b05b77cad4642ebe7 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 29 Sep 2022 19:42:11 +0200 Subject: [PATCH 15/35] fix tests Signed-off-by: Kiss Miklos --- .../analyzers/GitHubLocationAnalyzer.test.ts | 21 ++++++++----------- .../src/lib/index.ts | 1 - 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts index 5ceb9cf85c..f4996f232a 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts @@ -37,7 +37,7 @@ import { GitHubLocationAnalyzer } from './GitHubLocationAnalyzer'; import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; -import { GitHubIntegration } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; const server = setupServer(); @@ -46,17 +46,14 @@ describe('GitHubLocationAnalyzer', () => { getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7007'), getExternalBaseUrl: jest.fn(), }; - const integrations = { - list: jest.fn(), - byHost: jest.fn(), - byUrl: () => - new GitHubIntegration({ + const config = new ConfigReader({ + integrations: { + github: { host: 'h.com', - apiBaseUrl: 'a', - rawBaseUrl: 'r', token: 't', - }), - }; + }, + }, + }); setupRequestMockHandlers(server); @@ -117,7 +114,7 @@ describe('GitHubLocationAnalyzer', () => { const analyzer = new GitHubLocationAnalyzer({ discovery: mockDiscoveryApi, - integrations, + config, }); const result = await analyzer.analyze({ url: 'https://github.com/foo/bar', @@ -142,7 +139,7 @@ describe('GitHubLocationAnalyzer', () => { const analyzer = new GitHubLocationAnalyzer({ discovery: mockDiscoveryApi, - integrations, + config, }); const result = await analyzer.analyze({ url: 'https://github.com/foo/bar', diff --git a/plugins/catalog-backend-module-github/src/lib/index.ts b/plugins/catalog-backend-module-github/src/lib/index.ts index 28cbe898d6..26ec3e8516 100644 --- a/plugins/catalog-backend-module-github/src/lib/index.ts +++ b/plugins/catalog-backend-module-github/src/lib/index.ts @@ -20,7 +20,6 @@ export { getOrganizationRepositories, getOrganizationTeams, getOrganizationUsers, - getRepository, } from './github'; export { assignGroupsToUsers, buildOrgHierarchy } from './org'; export { parseGitHubOrgUrl } from './util'; From e74ac1c708e09bf4fff5020a017ab3244f066c65 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Fri, 30 Sep 2022 10:34:10 +0200 Subject: [PATCH 16/35] add analyzers to the CatalogBuilder Signed-off-by: Kiss Miklos --- .changeset/moody-carrots-shout.md | 2 +- .../api-report.md | 22 +++++++++++++++++++ .../src/analyzers/GitHubLocationAnalyzer.ts | 2 ++ .../src/index.ts | 1 + plugins/catalog-backend/api-report.md | 16 +++++++++++++- plugins/catalog-backend/package.json | 1 - .../src/ingestion/LocationAnalyzer.ts | 11 +++------- .../catalog-backend/src/ingestion/types.ts | 2 ++ .../src/service/CatalogBuilder.ts | 7 +----- .../src/service/CatalogPlugin.ts | 4 ---- .../src/service/standaloneServer.ts | 1 - 11 files changed, 47 insertions(+), 22 deletions(-) diff --git a/.changeset/moody-carrots-shout.md b/.changeset/moody-carrots-shout.md index a1c357041f..1b1555a5dd 100644 --- a/.changeset/moody-carrots-shout.md +++ b/.changeset/moody-carrots-shout.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend-module-github': minor --- -Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given url that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. +Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 8e8055bafc..0d6c8c015f 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -3,6 +3,8 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-backend'; +import { AnalyzeOptions } from '@backstage/plugin-catalog-backend'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; @@ -13,7 +15,9 @@ import { GithubCredentialsProvider } from '@backstage/integration'; import { GitHubIntegrationConfig } from '@backstage/integration'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-backend'; import { TaskRunner } from '@backstage/backend-tasks'; import { TaskScheduleDefinition } from '@backstage/backend-tasks'; @@ -70,6 +74,24 @@ export type GithubEntityProviderCatalogModuleOptions = { schedule?: TaskScheduleDefinition; }; +// @public (undocumented) +export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { + constructor(options: GitHubLocationAnalyzerOptions); + // (undocumented) + analyze({ + url, + catalogFilename, + }: AnalyzeOptions): Promise; + // (undocumented) + getIntegrationType(): string; +} + +// @public (undocumented) +export type GitHubLocationAnalyzerOptions = { + config: Config; + discovery: PluginEndpointDiscovery; +}; + // @public export type GithubMultiOrgConfig = Array<{ name: string; diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts index 7ab92565ce..a043b894cd 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts @@ -27,10 +27,12 @@ import { import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { Config } from '@backstage/config'; +/** @public */ export type GitHubLocationAnalyzerOptions = { config: Config; discovery: PluginEndpointDiscovery; }; +/** @public */ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { private readonly catalogClient: CatalogApi; private readonly config: Config; diff --git a/plugins/catalog-backend-module-github/src/index.ts b/plugins/catalog-backend-module-github/src/index.ts index e856277873..ca7c72f11b 100644 --- a/plugins/catalog-backend-module-github/src/index.ts +++ b/plugins/catalog-backend-module-github/src/index.ts @@ -30,3 +30,4 @@ export type { GithubMultiOrgConfig } from './lib'; export { githubEntityProviderCatalogModule } from './module'; export type { GithubEntityProviderCatalogModuleOptions } from './module'; export { GitHubLocationAnalyzer } from './analyzers/GitHubLocationAnalyzer'; +export type { GitHubLocationAnalyzerOptions } from './analyzers/GitHubLocationAnalyzer'; diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index bf53badd87..15cb23d1aa 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -87,6 +87,12 @@ export type AnalyzeLocationResponse = { generateEntities: AnalyzeLocationGenerateEntity[]; }; +// @public (undocumented) +export type AnalyzeOptions = { + url: string; + catalogFilename?: string; +}; + // @public (undocumented) export class AnnotateLocationEntityProcessor implements CatalogProcessor { constructor(options: { integrations: ScmIntegrationRegistry }); @@ -134,6 +140,9 @@ export class CatalogBuilder { addEntityProvider( ...providers: Array> ): CatalogBuilder; + addLocationAnalyzers( + ...analyzers: Array> + ): CatalogBuilder; // @alpha addPermissionRules( ...permissionRules: Array< @@ -219,7 +228,6 @@ export type CatalogEnvironment = { config: Config; reader: UrlReader; permissions: PermissionEvaluator | PermissionAuthorizer; - discovery: PluginEndpointDiscovery; }; // @alpha @@ -533,6 +541,12 @@ export type ProcessingIntervalFunction = () => number; export { processingResult }; +// @public (undocumented) +export type ScmLocationAnalyzer = { + getIntegrationType(): string; + analyze(options: AnalyzeOptions): Promise; +}; + // @public (undocumented) export class UrlReaderProcessor implements CatalogProcessor { constructor(options: { reader: UrlReader; logger: Logger }); diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 8232f1d05a..f5ab216a7f 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -47,7 +47,6 @@ "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-search-common": "workspace:^", "@backstage/types": "workspace:^", - "@octokit/rest": "^19.0.4", "@types/express": "^4.17.6", "codeowners-utils": "^1.0.2", "core-js": "^3.6.5", diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index 7992903b65..11f9799759 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -17,10 +17,7 @@ import { Logger } from 'winston'; import parseGitUrl from 'git-url-parse'; import { Entity } from '@backstage/catalog-model'; -import { - GitHubIntegration, - ScmIntegrationRegistry, -} from '@backstage/integration'; +import { ScmIntegrationRegistry } from '@backstage/integration'; import { AnalyzeLocationRequest, AnalyzeLocationResponse, @@ -45,9 +42,7 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { async analyzeLocation( request: AnalyzeLocationRequest, ): Promise { - const integration = this.scmIntegrations.byUrl( - request.location.target, - ) as GitHubIntegration; + const integration = this.scmIntegrations.byUrl(request.location.target); const { owner, name } = parseGitUrl(request.location.target); let annotationPrefix; @@ -69,7 +64,7 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { } const analyzer = this.analyzers.find( - a => a.getIntegrationType() === integration.type, + a => a.getIntegrationType() === integration?.type, ); if (analyzer) { const existingEntityFiles = await analyzer.analyze({ diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 961be78572..b68a4681b1 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -100,10 +100,12 @@ export type AnalyzeLocationEntityField = { description: string; }; +/** @public */ export type AnalyzeOptions = { url: string; catalogFilename?: string; }; + /** @public */ export type ScmLocationAnalyzer = { /** The integration type this location analyzer can work with */ diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 97379ac491..e65625d637 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - PluginDatabaseManager, - PluginEndpointDiscovery, - UrlReader, -} from '@backstage/backend-common'; +import { PluginDatabaseManager, UrlReader } from '@backstage/backend-common'; import { DefaultNamespaceEntityPolicy, Entity, @@ -108,7 +104,6 @@ export type CatalogEnvironment = { config: Config; reader: UrlReader; permissions: PermissionEvaluator | PermissionAuthorizer; - discovery: PluginEndpointDiscovery; }; /** diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index 3619460c4d..64e1e52494 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -22,7 +22,6 @@ import { permissionsServiceRef, urlReaderServiceRef, httpRouterServiceRef, - discoveryServiceRef, } from '@backstage/backend-plugin-api'; import { CatalogBuilder } from './CatalogBuilder'; import { @@ -79,7 +78,6 @@ export const catalogPlugin = createBackendPlugin({ permissions: permissionsServiceRef, database: databaseServiceRef, httpRouter: httpRouterServiceRef, - discovery: discoveryServiceRef, }, async init({ logger, @@ -88,7 +86,6 @@ export const catalogPlugin = createBackendPlugin({ database, permissions, httpRouter, - discovery, }) { const winstonLogger = loggerToWinstonLogger(logger); const builder = await CatalogBuilder.create({ @@ -97,7 +94,6 @@ export const catalogPlugin = createBackendPlugin({ permissions, database, logger: winstonLogger, - discovery, }); builder.addProcessor(...processingExtensions.processors); builder.addEntityProvider(...processingExtensions.entityProviders); diff --git a/plugins/catalog-backend/src/service/standaloneServer.ts b/plugins/catalog-backend/src/service/standaloneServer.ts index 51195ccdd7..e48515fb8b 100644 --- a/plugins/catalog-backend/src/service/standaloneServer.ts +++ b/plugins/catalog-backend/src/service/standaloneServer.ts @@ -70,7 +70,6 @@ export async function startStandaloneServer( config, reader, permissions, - discovery, }); const catalog = await builder.build(); From 6dc9afcb16276cb2443ebe050266ad56cecd5785 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Fri, 30 Sep 2022 10:43:57 +0200 Subject: [PATCH 17/35] add yarn.lock Signed-off-by: Kiss Miklos --- yarn.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index a418c7e297..8fbe52e29a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4672,7 +4672,6 @@ __metadata: "@backstage/plugin-search-backend-node": "workspace:^" "@backstage/plugin-search-common": "workspace:^" "@backstage/types": "workspace:^" - "@octokit/rest": ^19.0.4 "@types/core-js": ^2.5.4 "@types/express": ^4.17.6 "@types/git-url-parse": ^9.0.0 From c6e3dfee15e15049efa9c5e75edb7222ce0f45d0 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Fri, 30 Sep 2022 13:19:50 +0200 Subject: [PATCH 18/35] fix tests Signed-off-by: Kiss Miklos --- .../src/analyzers/GitHubLocationAnalyzer.test.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts index f4996f232a..9502c47c6e 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts @@ -48,10 +48,12 @@ describe('GitHubLocationAnalyzer', () => { }; const config = new ConfigReader({ integrations: { - github: { - host: 'h.com', - token: 't', - }, + github: [ + { + host: 'h.com', + token: 't', + }, + ], }, }); From 21b0e398a6106c3bc5f6c6c8716c6b27bf3bca2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikl=C3=B3s=20Kiss?= Date: Mon, 3 Oct 2022 14:54:14 +0200 Subject: [PATCH 19/35] Update plugins/catalog-backend/src/service/CatalogBuilder.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johan Haals Signed-off-by: Miklós Kiss --- plugins/catalog-backend/src/service/CatalogBuilder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index e65625d637..5a737791ae 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -116,7 +116,7 @@ export type CatalogEnvironment = { * chance to inspect the entity, and all of them have to pass in order for * the entity to be considered valid from an overall point of view. * - Location analyzers can be added. These are responsible to analyze the - * the existence of a catalog-info.yaml file int he provided git repository + * the existence of a catalog-info.yaml file in the provided git repository * when you use the /catalog-import page with a repository url. * - Placeholder resolvers can be replaced or added. These run on the raw * structured data between the parsing and pre-processing steps, to replace From e8357aae54bad6e2a26fa6bb52c64b50e36b7e26 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 3 Oct 2022 15:10:14 +0200 Subject: [PATCH 20/35] add more details to changesets Signed-off-by: Kiss Miklos --- .changeset/curvy-pets-wash.md | 4 ++-- .changeset/fuzzy-dolls-shake.md | 20 +++++++++++++++++-- .changeset/moody-carrots-shout.md | 2 +- .../src/service/CatalogBuilder.ts | 2 +- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.changeset/curvy-pets-wash.md b/.changeset/curvy-pets-wash.md index 7e43228fbc..a0e9571b18 100644 --- a/.changeset/curvy-pets-wash.md +++ b/.changeset/curvy-pets-wash.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog-backend': minor --- -Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials +Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. diff --git a/.changeset/fuzzy-dolls-shake.md b/.changeset/fuzzy-dolls-shake.md index 92380b37ec..0148a703a4 100644 --- a/.changeset/fuzzy-dolls-shake.md +++ b/.changeset/fuzzy-dolls-shake.md @@ -1,5 +1,21 @@ --- -'@backstage/plugin-catalog-import': minor +'@backstage/plugin-catalog-import': patch --- -Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials +**Breaking** +Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials. + +Add the following to your `CatalogBuilder` to have the repo URL ingestion working again. + +```ts +// catalog.ts +import { GitHubLocationAnalyzer } from '@backstage/plugin-catalog-backend-module-github'; +... + builder.addLocationAnalyzers( + new GitHubLocationAnalyzer({ + discovery: env.discovery, + config: env.config, + }), + ); +... +``` diff --git a/.changeset/moody-carrots-shout.md b/.changeset/moody-carrots-shout.md index 1b1555a5dd..486590e0ba 100644 --- a/.changeset/moody-carrots-shout.md +++ b/.changeset/moody-carrots-shout.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-backend-module-github': minor +'@backstage/plugin-catalog-backend-module-github': patch --- Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 5a737791ae..deed865314 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -343,7 +343,7 @@ export class CatalogBuilder { /** * Adds Location Analyzers. These are responsible for figuring out * if the repository already contains a catalog-info.yaml file when - * you register a repostiroy in the /catalog-import page + * you register a repository in the /catalog-import page * * @param locationAnalyzers - One or more location analyzers */ From 41de015847824dff329e5beb3f276b8a26d19040 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 3 Oct 2022 16:34:53 +0200 Subject: [PATCH 21/35] make breaking change a minor change Signed-off-by: Kiss Miklos --- .changeset/fuzzy-dolls-shake.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/fuzzy-dolls-shake.md b/.changeset/fuzzy-dolls-shake.md index 0148a703a4..35ebcf6103 100644 --- a/.changeset/fuzzy-dolls-shake.md +++ b/.changeset/fuzzy-dolls-shake.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-import': patch +'@backstage/plugin-catalog-import': minor --- **Breaking** From 290a8cd57730a62519a2528a9e5789f4b2202776 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 3 Oct 2022 17:28:34 +0200 Subject: [PATCH 22/35] use the existing versions Signed-off-by: Kiss Miklos --- plugins/catalog-backend-module-github/package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 950c7723e8..f99b951149 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -45,8 +45,8 @@ "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", "@octokit/graphql": "^5.0.0", - "@octokit/rest": "^19.0.4", - "git-url-parse": "^13.1.0", + "@octokit/rest": "^19.0.3", + "git-url-parse": "^13.0.0", "lodash": "^4.17.21", "msw": "^0.47.0", "node-fetch": "^2.6.7", diff --git a/yarn.lock b/yarn.lock index 8fbe52e29a..5563c9ffbe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4551,9 +4551,9 @@ __metadata: "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" "@octokit/graphql": ^5.0.0 - "@octokit/rest": ^19.0.4 + "@octokit/rest": ^19.0.3 "@types/lodash": ^4.14.151 - git-url-parse: ^13.1.0 + git-url-parse: ^13.0.0 lodash: ^4.17.21 msw: ^0.47.0 node-fetch: ^2.6.7 @@ -11580,7 +11580,7 @@ __metadata: languageName: node linkType: hard -"@octokit/rest@npm:^19.0.3, @octokit/rest@npm:^19.0.4": +"@octokit/rest@npm:^19.0.3": version: 19.0.4 resolution: "@octokit/rest@npm:19.0.4" dependencies: @@ -23513,7 +23513,7 @@ __metadata: languageName: node linkType: hard -"git-url-parse@npm:^13.0.0, git-url-parse@npm:^13.1.0": +"git-url-parse@npm:^13.0.0": version: 13.1.0 resolution: "git-url-parse@npm:13.1.0" dependencies: From 1effb063c2d073f458dfbf4a43d56dfcb144f30b Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 3 Oct 2022 17:37:27 +0200 Subject: [PATCH 23/35] more general description comment Signed-off-by: Kiss Miklos --- .../catalog-backend/src/service/CatalogBuilder.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index deed865314..d5bc7057dd 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -115,9 +115,10 @@ export type CatalogEnvironment = { * after the processors' pre-processing steps. All policies are given the * chance to inspect the entity, and all of them have to pass in order for * the entity to be considered valid from an overall point of view. - * - Location analyzers can be added. These are responsible to analyze the - * the existence of a catalog-info.yaml file in the provided git repository - * when you use the /catalog-import page with a repository url. + * - Location analyzers can be added. These are responsible for analyzing + * repositories when onboarding them into the catalog, by finding + * catalog-info.yaml files and other artifacts that can help automatically + * register or create catalog data on the user's behalf. * - Placeholder resolvers can be replaced or added. These run on the raw * structured data between the parsing and pre-processing steps, to replace * dollar-prefixed entries with their actual values (like $file). @@ -341,9 +342,10 @@ export class CatalogBuilder { } /** - * Adds Location Analyzers. These are responsible for figuring out - * if the repository already contains a catalog-info.yaml file when - * you register a repository in the /catalog-import page + * Adds Location Analyzers. These are responsible for analyzing + * repositories when onboarding them into the catalog, by finding + * catalog-info.yaml files and other artifacts that can help automatically + * register or create catalog data on the user's behalf. * * @param locationAnalyzers - One or more location analyzers */ From 99cf6b11b1c6ae9cd2223cd4c99c39acc029d817 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 3 Oct 2022 17:38:33 +0200 Subject: [PATCH 24/35] don't use any Signed-off-by: Kiss Miklos --- plugins/catalog-import/src/api/CatalogImportClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index 5dd3a3178f..d019b40045 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -145,7 +145,7 @@ export class CatalogImportClient implements CatalogImportApi { type: 'repository', integrationType: 'github', url: url, - generatedEntities: analyzation.generateEntities.map((x: any) => x.entity), + generatedEntities: analyzation.generateEntities.map(x => x.entity), }; } From a18c07d308616660bea9b38a53aa5fec0021dc11 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 3 Oct 2022 19:24:36 +0200 Subject: [PATCH 25/35] make the return of analyze an object Signed-off-by: Kiss Miklos --- .../src/analyzers/GitHubLocationAnalyzer.test.ts | 6 +++--- .../src/analyzers/GitHubLocationAnalyzer.ts | 9 +++------ .../catalog-backend/src/ingestion/LocationAnalyzer.ts | 6 +++--- plugins/catalog-backend/src/ingestion/types.ts | 6 ++++-- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts index 9502c47c6e..0dcc131502 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts @@ -122,8 +122,8 @@ describe('GitHubLocationAnalyzer', () => { url: 'https://github.com/foo/bar', }); - expect(result[0].isRegistered).toBeFalsy(); - expect(result[0].location).toEqual({ + expect(result.existing[0].isRegistered).toBeFalsy(); + expect(result.existing[0].location).toEqual({ type: 'url', target: 'https://github.com/foo/bar/blob/my_default_branch/catalog-info.yaml', @@ -148,7 +148,7 @@ describe('GitHubLocationAnalyzer', () => { catalogFilename: 'anvil.yaml', }); - expect(result[0].location).toEqual({ + expect(result.existing[0].location).toEqual({ type: 'url', target: 'https://github.com/foo/bar/blob/my_default_branch/anvil.yaml', }); diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts index a043b894cd..085f82de49 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts @@ -44,10 +44,7 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { getIntegrationType() { return 'github'; } - async analyze({ - url, - catalogFilename, - }: AnalyzeOptions): Promise { + async analyze({ url, catalogFilename }: AnalyzeOptions) { const { owner, name: repo } = parseGitUrl(url); const catalogFile = catalogFilename || 'catalog-info.yaml'; @@ -98,8 +95,8 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { }), ); - return result.flat(); + return { existing: result.flat() }; } - return []; + return { existing: [] }; } } diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index 11f9799759..b863f00f2c 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -67,15 +67,15 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { a => a.getIntegrationType() === integration?.type, ); if (analyzer) { - const existingEntityFiles = await analyzer.analyze({ + const analyzerResult = await analyzer.analyze({ url: request.location.target, }); - if (existingEntityFiles.length > 0) { + if (analyzerResult.existing.length > 0) { this.logger.debug( `entity for ${request.location.target} already exists.`, ); return { - existingEntityFiles, + existingEntityFiles: analyzerResult.existing, generateEntities: [], }; } diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index b68a4681b1..40b4574f78 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -110,6 +110,8 @@ export type AnalyzeOptions = { export type ScmLocationAnalyzer = { /** The integration type this location analyzer can work with */ getIntegrationType(): string; - /** This function is responsible to figure out if the catalog file is already present in the repository */ - analyze(options: AnalyzeOptions): Promise; + /** This function can return an array of already existing entities */ + analyze(options: AnalyzeOptions): Promise<{ + existing: AnalyzeLocationExistingEntity[]; + }>; }; From ce865b69cf6425742517cd8ce86ed013fe71c031 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Mon, 3 Oct 2022 19:55:50 +0200 Subject: [PATCH 26/35] invert the control on the analyzer support Signed-off-by: Kiss Miklos --- .../src/analyzers/GitHubLocationAnalyzer.ts | 7 ++++--- plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts | 4 ++-- plugins/catalog-backend/src/ingestion/types.ts | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts index 085f82de49..d1112d2cc0 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts @@ -20,7 +20,6 @@ import { Octokit } from '@octokit/rest'; import { trimEnd } from 'lodash'; import parseGitUrl from 'git-url-parse'; import { - AnalyzeLocationExistingEntity, AnalyzeOptions, ScmLocationAnalyzer, } from '@backstage/plugin-catalog-backend'; @@ -41,8 +40,10 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { this.config = options.config; this.catalogClient = new CatalogClient({ discoveryApi: options.discovery }); } - getIntegrationType() { - return 'github'; + supports(url: string) { + const integrations = ScmIntegrations.fromConfig(this.config); + const integration = integrations.byUrl(url); + return integration?.type === 'github'; } async analyze({ url, catalogFilename }: AnalyzeOptions) { const { owner, name: repo } = parseGitUrl(url); diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index b863f00f2c..3dc08a81c3 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -63,8 +63,8 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { break; } - const analyzer = this.analyzers.find( - a => a.getIntegrationType() === integration?.type, + const analyzer = this.analyzers.find(a => + a.supports(request.location.target), ); if (analyzer) { const analyzerResult = await analyzer.analyze({ diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 40b4574f78..c5237af6d3 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -108,8 +108,8 @@ export type AnalyzeOptions = { /** @public */ export type ScmLocationAnalyzer = { - /** The integration type this location analyzer can work with */ - getIntegrationType(): string; + /** The method that decides if this analyzer can work with the provided url */ + supports(url: string): boolean; /** This function can return an array of already existing entities */ analyze(options: AnalyzeOptions): Promise<{ existing: AnalyzeLocationExistingEntity[]; From 41568be052894270a0f4dd697cc1035fb13cf56d Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Tue, 4 Oct 2022 00:50:01 +0200 Subject: [PATCH 27/35] generate api-reports Signed-off-by: Kiss Miklos --- .../api-report.md | 18 ++++++++++++------ plugins/catalog-backend/api-report.md | 6 ++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 0d6c8c015f..c89a34c259 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -3,12 +3,12 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-backend'; import { AnalyzeOptions } from '@backstage/plugin-catalog-backend'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; import { Config } from '@backstage/config'; +import { Entity } from '@backstage/catalog-model'; import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { GithubCredentialsProvider } from '@backstage/integration'; @@ -78,12 +78,18 @@ export type GithubEntityProviderCatalogModuleOptions = { export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { constructor(options: GitHubLocationAnalyzerOptions); // (undocumented) - analyze({ - url, - catalogFilename, - }: AnalyzeOptions): Promise; + analyze({ url, catalogFilename }: AnalyzeOptions): Promise<{ + existing: { + location: { + type: string; + target: string; + }; + isRegistered: boolean; + entity: Entity; + }[]; + }>; // (undocumented) - getIntegrationType(): string; + supports(url: string): boolean; } // @public (undocumented) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 15cb23d1aa..ede024e296 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -543,8 +543,10 @@ export { processingResult }; // @public (undocumented) export type ScmLocationAnalyzer = { - getIntegrationType(): string; - analyze(options: AnalyzeOptions): Promise; + supports(url: string): boolean; + analyze(options: AnalyzeOptions): Promise<{ + existing: AnalyzeLocationExistingEntity[]; + }>; }; // @public (undocumented) From 5e8c2682fe307e2f8eb197c026d00f065376a841 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Wed, 5 Oct 2022 13:39:19 +0200 Subject: [PATCH 28/35] move types to catalog-common Signed-off-by: Kiss Miklos --- .../src/ingestion/LocationAnalyzer.ts | 5 +- .../catalog-backend/src/ingestion/index.ts | 5 -- .../catalog-backend/src/ingestion/types.ts | 78 ++-------------- plugins/catalog-common/package.json | 2 + plugins/catalog-common/src/index.ts | 1 + .../src/ingestion/LocationAnalyzer.ts | 88 +++++++++++++++++++ .../src/ingestion/RecursivePartial.test.ts | 31 +++++++ .../src/ingestion/RecursivePartial.ts | 27 ++++++ plugins/catalog-common/src/ingestion/index.ts | 21 +++++ plugins/catalog-import/package.json | 2 +- .../src/api/CatalogImportClient.ts | 2 +- yarn.lock | 4 +- 12 files changed, 182 insertions(+), 84 deletions(-) create mode 100644 plugins/catalog-common/src/ingestion/LocationAnalyzer.ts create mode 100644 plugins/catalog-common/src/ingestion/RecursivePartial.test.ts create mode 100644 plugins/catalog-common/src/ingestion/RecursivePartial.ts create mode 100644 plugins/catalog-common/src/ingestion/index.ts diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index 3dc08a81c3..761a0f8c0a 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -18,12 +18,11 @@ import { Logger } from 'winston'; import parseGitUrl from 'git-url-parse'; import { Entity } from '@backstage/catalog-model'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { LocationAnalyzer, ScmLocationAnalyzer } from './types'; import { AnalyzeLocationRequest, AnalyzeLocationResponse, - LocationAnalyzer, - ScmLocationAnalyzer, -} from './types'; +} from '@backstage/plugin-catalog-common'; export class RepoLocationAnalyzer implements LocationAnalyzer { private readonly logger: Logger; diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts index 0b00809043..0fc71f6785 100644 --- a/plugins/catalog-backend/src/ingestion/index.ts +++ b/plugins/catalog-backend/src/ingestion/index.ts @@ -15,11 +15,6 @@ */ export type { - AnalyzeLocationEntityField, - AnalyzeLocationExistingEntity, - AnalyzeLocationGenerateEntity, - AnalyzeLocationRequest, - AnalyzeLocationResponse, LocationAnalyzer, ScmLocationAnalyzer, AnalyzeOptions, diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index c5237af6d3..c6a480a7f5 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { Entity } from '@backstage/catalog-model'; -import { RecursivePartial } from '../util/RecursivePartial'; -import { LocationSpec } from '@backstage/plugin-catalog-node'; +import { + AnalyzeLocationResponse, + AnalyzeLocationRequest, + AnalyzeLocationExistingEntity, +} from '@backstage/plugin-catalog-common'; /** @public */ export type LocationAnalyzer = { @@ -31,75 +32,6 @@ export type LocationAnalyzer = { ): Promise; }; -/** @public */ -export type AnalyzeLocationRequest = { - location: LocationSpec; - catalogFilename?: string; -}; - -/** @public */ -export type AnalyzeLocationResponse = { - existingEntityFiles: AnalyzeLocationExistingEntity[]; - generateEntities: AnalyzeLocationGenerateEntity[]; -}; - -/** - * 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 - */ -export type AnalyzeLocationExistingEntity = { - location: LocationSpec; - isRegistered: boolean; - entity: Entity; -}; - -/** - * 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 - */ -export type AnalyzeLocationGenerateEntity = { - // Some form of partial representation of the entity - entity: RecursivePartial; - // Lists the suggestions that the user may want to override - fields: AnalyzeLocationEntityField[]; -}; - -// 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 */ -export type AnalyzeLocationEntityField = { - /** - * e.g. "spec.owner"? The frontend needs to know how to "inject" the field into the - * entity again if the user wants to change it - */ - field: string; - - /** The outcome of the analysis for this particular field */ - state: - | 'analysisSuggestedValue' - | 'analysisSuggestedNoValue' - | 'needsUserInput'; - - // If the analysis did suggest a value, this is where it would be. Not sure if we want - // to limit this to strings or if we want it to be any JsonValue - value: string | null; - /** - * A text to show to the user to inform about the choices made. Like, it could say - * "Found a CODEOWNERS file that covers this target, so we suggest leaving this - * field empty; which would currently make it owned by X" where X is taken from the - * codeowners file. - */ - description: string; -}; - /** @public */ export type AnalyzeOptions = { url: string; diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index ecb53d35de..96e9f31b43 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -33,6 +33,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { + "@backstage/catalog-model": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-search-common": "workspace:^" }, diff --git a/plugins/catalog-common/src/index.ts b/plugins/catalog-common/src/index.ts index 614948e56c..a37fd122f9 100644 --- a/plugins/catalog-common/src/index.ts +++ b/plugins/catalog-common/src/index.ts @@ -35,3 +35,4 @@ export { export type { CatalogEntityPermission } from './permissions'; export * from './search'; +export * from './ingestion'; diff --git a/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts new file mode 100644 index 0000000000..8d570894cf --- /dev/null +++ b/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts @@ -0,0 +1,88 @@ +/* + * 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 { LocationSpec } from '@backstage/plugin-catalog-node'; +import { Entity } from '@backstage/catalog-model'; +import { RecursivePartial } from './RecursivePartial'; + +/** @public */ +export type AnalyzeLocationRequest = { + location: LocationSpec; + catalogFilename?: string; +}; + +/** @public */ +export type AnalyzeLocationResponse = { + existingEntityFiles: AnalyzeLocationExistingEntity[]; + generateEntities: AnalyzeLocationGenerateEntity[]; +}; + +/** + * 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 + */ +export type AnalyzeLocationExistingEntity = { + location: LocationSpec; + isRegistered: boolean; + entity: Entity; +}; + +/** + * 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 + */ +export type AnalyzeLocationGenerateEntity = { + // Some form of partial representation of the entity + entity: RecursivePartial; + // Lists the suggestions that the user may want to override + fields: AnalyzeLocationEntityField[]; +}; + +// 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 */ +export type AnalyzeLocationEntityField = { + /** + * e.g. "spec.owner"? The frontend needs to know how to "inject" the field into the + * entity again if the user wants to change it + */ + field: string; + + /** The outcome of the analysis for this particular field */ + state: + | 'analysisSuggestedValue' + | 'analysisSuggestedNoValue' + | 'needsUserInput'; + + // If the analysis did suggest a value, this is where it would be. Not sure if we want + // to limit this to strings or if we want it to be any JsonValue + value: string | null; + /** + * A text to show to the user to inform about the choices made. Like, it could say + * "Found a CODEOWNERS file that covers this target, so we suggest leaving this + * field empty; which would currently make it owned by X" where X is taken from the + * codeowners file. + */ + description: string; +}; diff --git a/plugins/catalog-common/src/ingestion/RecursivePartial.test.ts b/plugins/catalog-common/src/ingestion/RecursivePartial.test.ts new file mode 100644 index 0000000000..ab8d50534e --- /dev/null +++ b/plugins/catalog-common/src/ingestion/RecursivePartial.test.ts @@ -0,0 +1,31 @@ +/* + * 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 { RecursivePartial } from './RecursivePartial'; + +describe('RecursivePartial', () => { + it('is recursive', () => { + type X = { + required: { + required: string; + }; + }; + const x: RecursivePartial = { + required: {}, + }; + expect(x).toEqual({ required: {} }); + }); +}); diff --git a/plugins/catalog-common/src/ingestion/RecursivePartial.ts b/plugins/catalog-common/src/ingestion/RecursivePartial.ts new file mode 100644 index 0000000000..c452836f34 --- /dev/null +++ b/plugins/catalog-common/src/ingestion/RecursivePartial.ts @@ -0,0 +1,27 @@ +/* + * 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. + */ + +/** + * Makes all keys of an entire hierarchy optional. + * @ignore + */ +export type RecursivePartial = { + [P in keyof T]?: T[P] extends (infer U)[] + ? RecursivePartial[] + : T[P] extends object + ? RecursivePartial + : T[P]; +}; diff --git a/plugins/catalog-common/src/ingestion/index.ts b/plugins/catalog-common/src/ingestion/index.ts new file mode 100644 index 0000000000..ceb2ff1dfc --- /dev/null +++ b/plugins/catalog-common/src/ingestion/index.ts @@ -0,0 +1,21 @@ +/* + * 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 { + AnalyzeLocationResponse, + AnalyzeLocationRequest, + AnalyzeLocationExistingEntity, +} from './LocationAnalyzer'; diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 055955c9c7..224a30bdb2 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -40,7 +40,7 @@ "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/integration-react": "workspace:^", - "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index d019b40045..0663122253 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -30,7 +30,7 @@ import { Base64 } from 'js-base64'; import { AnalyzeResult, CatalogImportApi } from './CatalogImportApi'; import { getGithubIntegrationConfig } from './GitHub'; import { getBranchName, getCatalogFilename } from '../components/helpers'; -import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend'; +import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-common'; import { CompoundEntityRef } from '@backstage/catalog-model'; /** diff --git a/yarn.lock b/yarn.lock index 5563c9ffbe..71d139f97d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4718,7 +4718,9 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-common@workspace:plugins/catalog-common" dependencies: + "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-search-common": "workspace:^" languageName: unknown @@ -4799,7 +4801,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/integration-react": "workspace:^" - "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@material-ui/core": ^4.12.2 From 148d33fa855df14ddac45a10a5c99693e6833c00 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Wed, 5 Oct 2022 14:09:22 +0200 Subject: [PATCH 29/35] add api-reports Signed-off-by: Kiss Miklos --- plugins/catalog-backend/api-report.md | 39 ++----------------- plugins/catalog-common/api-report.md | 38 ++++++++++++++++++ plugins/catalog-common/src/ingestion/index.ts | 2 + 3 files changed, 43 insertions(+), 36 deletions(-) diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index ede024e296..f5922d51ab 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -5,6 +5,9 @@ ```ts /// +import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationRequest } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-common'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { CatalogEntityDocument } from '@backstage/plugin-catalog-common'; @@ -51,42 +54,6 @@ import { TokenManager } from '@backstage/backend-common'; import { UrlReader } from '@backstage/backend-common'; import { Validators } from '@backstage/catalog-model'; -// @public (undocumented) -export type AnalyzeLocationEntityField = { - field: string; - state: - | 'analysisSuggestedValue' - | 'analysisSuggestedNoValue' - | 'needsUserInput'; - value: string | null; - description: string; -}; - -// @public -export type AnalyzeLocationExistingEntity = { - location: LocationSpec; - isRegistered: boolean; - entity: Entity; -}; - -// @public -export type AnalyzeLocationGenerateEntity = { - entity: RecursivePartial; - fields: AnalyzeLocationEntityField[]; -}; - -// @public (undocumented) -export type AnalyzeLocationRequest = { - location: LocationSpec; - catalogFilename?: string; -}; - -// @public (undocumented) -export type AnalyzeLocationResponse = { - existingEntityFiles: AnalyzeLocationExistingEntity[]; - generateEntities: AnalyzeLocationGenerateEntity[]; -}; - // @public (undocumented) export type AnalyzeOptions = { url: string; diff --git a/plugins/catalog-common/api-report.md b/plugins/catalog-common/api-report.md index b7eaf99d8b..867454f9bd 100644 --- a/plugins/catalog-common/api-report.md +++ b/plugins/catalog-common/api-report.md @@ -4,9 +4,47 @@ ```ts import { BasicPermission } from '@backstage/plugin-permission-common'; +import { Entity } from '@backstage/catalog-model'; import { IndexableDocument } from '@backstage/plugin-search-common'; +import { LocationSpec } from '@backstage/plugin-catalog-node'; import { ResourcePermission } from '@backstage/plugin-permission-common'; +// @public (undocumented) +export type AnalyzeLocationEntityField = { + field: string; + state: + | 'analysisSuggestedValue' + | 'analysisSuggestedNoValue' + | 'needsUserInput'; + value: string | null; + description: string; +}; + +// @public +export type AnalyzeLocationExistingEntity = { + location: LocationSpec; + isRegistered: boolean; + entity: Entity; +}; + +// @public +export type AnalyzeLocationGenerateEntity = { + entity: RecursivePartial; + fields: AnalyzeLocationEntityField[]; +}; + +// @public (undocumented) +export type AnalyzeLocationRequest = { + location: LocationSpec; + catalogFilename?: string; +}; + +// @public (undocumented) +export type AnalyzeLocationResponse = { + existingEntityFiles: AnalyzeLocationExistingEntity[]; + generateEntities: AnalyzeLocationGenerateEntity[]; +}; + // @alpha export const catalogEntityCreatePermission: BasicPermission; diff --git a/plugins/catalog-common/src/ingestion/index.ts b/plugins/catalog-common/src/ingestion/index.ts index ceb2ff1dfc..aced124bb4 100644 --- a/plugins/catalog-common/src/ingestion/index.ts +++ b/plugins/catalog-common/src/ingestion/index.ts @@ -18,4 +18,6 @@ export type { AnalyzeLocationResponse, AnalyzeLocationRequest, AnalyzeLocationExistingEntity, + AnalyzeLocationGenerateEntity, + AnalyzeLocationEntityField, } from './LocationAnalyzer'; From 823acaa88bd450a25a290f017b9e98a5965f1b69 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Wed, 5 Oct 2022 17:25:40 +0200 Subject: [PATCH 30/35] add changeset Signed-off-by: Kiss Miklos --- .changeset/five-tables-grow.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .changeset/five-tables-grow.md diff --git a/.changeset/five-tables-grow.md b/.changeset/five-tables-grow.md new file mode 100644 index 0000000000..982b17b97b --- /dev/null +++ b/.changeset/five-tables-grow.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-catalog-common': patch +--- + +Moved the following types from `@backstage/plugin-catalog-backend` to this package. + +- AnalyzeLocationResponse +- AnalyzeLocationRequest +- AnalyzeLocationExistingEntity +- AnalyzeLocationGenerateEntity +- AnalyzeLocationEntityField From 4cc3dccc26cdae0dc7f4d40b1b286459df236286 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Thu, 6 Oct 2022 18:14:52 +0200 Subject: [PATCH 31/35] deprecate ingestions types after moving to common Signed-off-by: Kiss Miklos --- .changeset/curvy-pets-wash.md | 8 +++ plugins/catalog-backend/api-report.md | 23 ++++++-- .../catalog-backend/src/ingestion/index.ts | 5 ++ .../catalog-backend/src/ingestion/types.ts | 53 +++++++++++++++++-- 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/.changeset/curvy-pets-wash.md b/.changeset/curvy-pets-wash.md index a0e9571b18..4c824f9ef4 100644 --- a/.changeset/curvy-pets-wash.md +++ b/.changeset/curvy-pets-wash.md @@ -3,3 +3,11 @@ --- Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. + +Moved the following types from this package to `@backstage/plugin-catalog-backend`. + +- AnalyzeLocationResponse +- AnalyzeLocationRequest +- AnalyzeLocationExistingEntity +- AnalyzeLocationGenerateEntity +- AnalyzeLocationEntityField diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index f5922d51ab..d23c6f7519 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -5,9 +5,11 @@ ```ts /// -import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-common'; -import { AnalyzeLocationRequest } from '@backstage/plugin-catalog-common'; -import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-common'; +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 { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { CatalogEntityDocument } from '@backstage/plugin-catalog-common'; @@ -54,6 +56,21 @@ import { TokenManager } from '@backstage/backend-common'; import { UrlReader } from '@backstage/backend-common'; 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 (undocumented) export type AnalyzeOptions = { url: string; diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts index 0fc71f6785..0b00809043 100644 --- a/plugins/catalog-backend/src/ingestion/index.ts +++ b/plugins/catalog-backend/src/ingestion/index.ts @@ -15,6 +15,11 @@ */ export type { + AnalyzeLocationEntityField, + AnalyzeLocationExistingEntity, + AnalyzeLocationGenerateEntity, + AnalyzeLocationRequest, + AnalyzeLocationResponse, LocationAnalyzer, ScmLocationAnalyzer, AnalyzeOptions, diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index c6a480a7f5..467fe498b2 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -13,12 +13,58 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { - AnalyzeLocationResponse, - AnalyzeLocationRequest, - AnalyzeLocationExistingEntity, + AnalyzeLocationRequest as ExaltedAnalyzeLocationRequest, + AnalyzeLocationResponse as ExaltedAnalyzeLocationResponse, + AnalyzeLocationExistingEntity as ExaltedAnalyzeLocationExistingEntity, + AnalyzeLocationGenerateEntity as ExaltedAnalyzeLocationGenerateEntity, + AnalyzeLocationEntityField as ExaltedAnalyzeLocationEntityField, } from '@backstage/plugin-catalog-common'; +/** + * @public + * @deprecated use the same type from `@backstage/plugin-catalog-common` instead + */ +export type AnalyzeLocationRequest = ExaltedAnalyzeLocationRequest; +/** + * @public + * @deprecated use the same type from `@backstage/plugin-catalog-common` instead + */ +export type AnalyzeLocationResponse = ExaltedAnalyzeLocationResponse; + +/** + * 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 = + ExaltedAnalyzeLocationExistingEntity; +/** + * 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 = + ExaltedAnalyzeLocationGenerateEntity; + +/** + * + * 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 = ExaltedAnalyzeLocationEntityField; + /** @public */ export type LocationAnalyzer = { /** @@ -31,7 +77,6 @@ export type LocationAnalyzer = { location: AnalyzeLocationRequest, ): Promise; }; - /** @public */ export type AnalyzeOptions = { url: string; From 64dea77ede13f98efb527d7ad8dfd1b6bb958ec7 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Fri, 7 Oct 2022 12:52:33 +0200 Subject: [PATCH 32/35] move type to common package Signed-off-by: Kiss Miklos --- .../catalog-backend/src/ingestion/types.ts | 21 +++++------ plugins/catalog-common/api-report.md | 8 ++++- plugins/catalog-common/src/common.ts | 32 +++++++++++++++++ plugins/catalog-common/src/index.ts | 1 + .../src/ingestion/LocationAnalyzer.ts | 2 +- plugins/catalog-node/api-report.md | 35 +++++++++---------- plugins/catalog-node/package.json | 1 + plugins/catalog-node/src/api/common.ts | 8 ++--- .../catalog-node/src/api/processingResult.ts | 3 +- plugins/catalog-node/src/api/processor.ts | 3 +- yarn.lock | 1 + 11 files changed, 77 insertions(+), 38 deletions(-) create mode 100644 plugins/catalog-common/src/common.ts diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 467fe498b2..416fa968ab 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -15,23 +15,23 @@ */ import { - AnalyzeLocationRequest as ExaltedAnalyzeLocationRequest, - AnalyzeLocationResponse as ExaltedAnalyzeLocationResponse, - AnalyzeLocationExistingEntity as ExaltedAnalyzeLocationExistingEntity, - AnalyzeLocationGenerateEntity as ExaltedAnalyzeLocationGenerateEntity, - AnalyzeLocationEntityField as ExaltedAnalyzeLocationEntityField, + AnalyzeLocationRequest as NonDeprecatedAnalyzeLocationRequest, + AnalyzeLocationResponse as NonDeprecatedAnalyzeLocationResponse, + AnalyzeLocationExistingEntity as NonDeprecatedAnalyzeLocationExistingEntity, + AnalyzeLocationGenerateEntity as NonDeprecatedAnalyzeLocationGenerateEntity, + AnalyzeLocationEntityField as NonDeprecatedAnalyzeLocationEntityField, } from '@backstage/plugin-catalog-common'; /** * @public * @deprecated use the same type from `@backstage/plugin-catalog-common` instead */ -export type AnalyzeLocationRequest = ExaltedAnalyzeLocationRequest; +export type AnalyzeLocationRequest = NonDeprecatedAnalyzeLocationRequest; /** * @public * @deprecated use the same type from `@backstage/plugin-catalog-common` instead */ -export type AnalyzeLocationResponse = ExaltedAnalyzeLocationResponse; +export type AnalyzeLocationResponse = NonDeprecatedAnalyzeLocationResponse; /** * If the folder pointed to already contained catalog info yaml files, they are @@ -42,7 +42,7 @@ export type AnalyzeLocationResponse = ExaltedAnalyzeLocationResponse; * @deprecated use the same type from `@backstage/plugin-catalog-common` instead */ export type AnalyzeLocationExistingEntity = - ExaltedAnalyzeLocationExistingEntity; + NonDeprecatedAnalyzeLocationExistingEntity; /** * 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 @@ -53,7 +53,7 @@ export type AnalyzeLocationExistingEntity = * @deprecated use the same type from `@backstage/plugin-catalog-common` instead */ export type AnalyzeLocationGenerateEntity = - ExaltedAnalyzeLocationGenerateEntity; + NonDeprecatedAnalyzeLocationGenerateEntity; /** * @@ -63,7 +63,8 @@ export type AnalyzeLocationGenerateEntity = * @public * @deprecated use the same type from `@backstage/plugin-catalog-common` instead */ -export type AnalyzeLocationEntityField = ExaltedAnalyzeLocationEntityField; +export type AnalyzeLocationEntityField = + NonDeprecatedAnalyzeLocationEntityField; /** @public */ export type LocationAnalyzer = { diff --git a/plugins/catalog-common/api-report.md b/plugins/catalog-common/api-report.md index 867454f9bd..1bef626c79 100644 --- a/plugins/catalog-common/api-report.md +++ b/plugins/catalog-common/api-report.md @@ -6,7 +6,6 @@ import { BasicPermission } from '@backstage/plugin-permission-common'; import { Entity } from '@backstage/catalog-model'; import { IndexableDocument } from '@backstage/plugin-search-common'; -import { LocationSpec } from '@backstage/plugin-catalog-node'; import { ResourcePermission } from '@backstage/plugin-permission-common'; // @public (undocumented) @@ -93,6 +92,13 @@ export const catalogPermissions: ( | ResourcePermission<'catalog-entity'> )[]; +// @public +export type LocationSpec = { + type: string; + target: string; + presence?: 'optional' | 'required'; +}; + // @alpha export const RESOURCE_TYPE_CATALOG_ENTITY = 'catalog-entity'; ``` diff --git a/plugins/catalog-common/src/common.ts b/plugins/catalog-common/src/common.ts new file mode 100644 index 0000000000..c926a94b28 --- /dev/null +++ b/plugins/catalog-common/src/common.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * 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 + */ +export type LocationSpec = { + type: string; + target: string; + presence?: 'optional' | 'required'; +}; diff --git a/plugins/catalog-common/src/index.ts b/plugins/catalog-common/src/index.ts index a37fd122f9..95bd65bc92 100644 --- a/plugins/catalog-common/src/index.ts +++ b/plugins/catalog-common/src/index.ts @@ -36,3 +36,4 @@ export type { CatalogEntityPermission } from './permissions'; export * from './search'; export * from './ingestion'; +export type { LocationSpec } from './common'; diff --git a/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts index 8d570894cf..08ae5ef0cb 100644 --- a/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/plugin-catalog-node'; +import { LocationSpec } from '../common'; import { Entity } from '@backstage/catalog-model'; import { RecursivePartial } from './RecursivePartial'; diff --git a/plugins/catalog-node/api-report.md b/plugins/catalog-node/api-report.md index 525516be54..09a05ec7d9 100644 --- a/plugins/catalog-node/api-report.md +++ b/plugins/catalog-node/api-report.md @@ -10,6 +10,7 @@ import { CompoundEntityRef } from '@backstage/catalog-model'; import { Entity } from '@backstage/catalog-model'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { JsonValue } from '@backstage/types'; +import { LocationSpec as LocationSpec_2 } from '@backstage/plugin-catalog-common'; import { ServiceRef } from '@backstage/backend-plugin-api'; // @alpha (undocumented) @@ -31,7 +32,7 @@ export const catalogProcessingExtensionPoint: ExtensionPoint; preProcessEntity?( entity: Entity, - location: LocationSpec, + location: LocationSpec_2, emit: CatalogProcessorEmit, - originLocation: LocationSpec, + originLocation: LocationSpec_2, cache: CatalogProcessorCache, ): Promise; validateEntityKind?(entity: Entity): Promise; postProcessEntity?( entity: Entity, - location: LocationSpec, + location: LocationSpec_2, emit: CatalogProcessorEmit, cache: CatalogProcessorCache, ): Promise; @@ -66,26 +67,26 @@ export type CatalogProcessorEmit = (generated: CatalogProcessorResult) => void; export type CatalogProcessorEntityResult = { type: 'entity'; entity: Entity; - location: LocationSpec; + location: LocationSpec_2; }; // @public (undocumented) export type CatalogProcessorErrorResult = { type: 'error'; error: Error; - location: LocationSpec; + location: LocationSpec_2; }; // @public (undocumented) export type CatalogProcessorLocationResult = { type: 'location'; - location: LocationSpec; + location: LocationSpec_2; }; // @public export type CatalogProcessorParser = (options: { data: Buffer; - location: LocationSpec; + location: LocationSpec_2; }) => AsyncIterable; // @public (undocumented) @@ -153,30 +154,26 @@ export type EntityRelationSpec = { target: CompoundEntityRef; }; -// @public -export type LocationSpec = { - type: string; - target: string; - presence?: 'optional' | 'required'; -}; +// @public @deprecated +export type LocationSpec = LocationSpec_2; // @public export const processingResult: Readonly<{ readonly notFoundError: ( - atLocation: LocationSpec, + atLocation: LocationSpec_2, message: string, ) => CatalogProcessorResult; readonly inputError: ( - atLocation: LocationSpec, + atLocation: LocationSpec_2, message: string, ) => CatalogProcessorResult; readonly generalError: ( - atLocation: LocationSpec, + atLocation: LocationSpec_2, message: string, ) => CatalogProcessorResult; - readonly location: (newLocation: LocationSpec) => CatalogProcessorResult; + readonly location: (newLocation: LocationSpec_2) => CatalogProcessorResult; readonly entity: ( - atLocation: LocationSpec, + atLocation: LocationSpec_2, newEntity: Entity, ) => CatalogProcessorResult; readonly relation: (spec: EntityRelationSpec) => CatalogProcessorResult; diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 0a7e687bb7..7cbbac1531 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -28,6 +28,7 @@ "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/plugin-catalog-common": "workspace:^", "@backstage/types": "workspace:^" }, "devDependencies": { diff --git a/plugins/catalog-node/src/api/common.ts b/plugins/catalog-node/src/api/common.ts index f3b4a387ba..c91aeca2d9 100644 --- a/plugins/catalog-node/src/api/common.ts +++ b/plugins/catalog-node/src/api/common.ts @@ -15,6 +15,7 @@ */ import { CompoundEntityRef } from '@backstage/catalog-model'; +import { LocationSpec as NonDeprecatedLocationSpec } from '@backstage/plugin-catalog-common'; /** * Holds the entity location information. @@ -26,12 +27,9 @@ import { CompoundEntityRef } from '@backstage/catalog-model'; * default value: 'required'. * * @public + * @deprecated use the same type from `@backstage/plugin-catalog-common` instead */ -export type LocationSpec = { - type: string; - target: string; - presence?: 'optional' | 'required'; -}; +export type LocationSpec = NonDeprecatedLocationSpec; /** * Holds the relation data for entities. diff --git a/plugins/catalog-node/src/api/processingResult.ts b/plugins/catalog-node/src/api/processingResult.ts index 84f1f4b70d..d8b6f3ae02 100644 --- a/plugins/catalog-node/src/api/processingResult.ts +++ b/plugins/catalog-node/src/api/processingResult.ts @@ -17,7 +17,8 @@ import { InputError, NotFoundError } from '@backstage/errors'; import { Entity } from '@backstage/catalog-model'; import { CatalogProcessorResult } from './processor'; -import { EntityRelationSpec, LocationSpec } from './common'; +import { EntityRelationSpec } from './common'; +import { LocationSpec } from '@backstage/plugin-catalog-common'; /** * Factory functions for the standard processing result types. diff --git a/plugins/catalog-node/src/api/processor.ts b/plugins/catalog-node/src/api/processor.ts index 44e8b298b0..3ad080f014 100644 --- a/plugins/catalog-node/src/api/processor.ts +++ b/plugins/catalog-node/src/api/processor.ts @@ -16,7 +16,8 @@ import { Entity } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/types'; -import { EntityRelationSpec, LocationSpec } from './common'; +import { EntityRelationSpec } from './common'; +import { LocationSpec } from '@backstage/plugin-catalog-common'; /** * @public diff --git a/yarn.lock b/yarn.lock index 71d139f97d..aca25a583e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4838,6 +4838,7 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/plugin-catalog-common": "workspace:^" "@backstage/types": "workspace:^" languageName: unknown linkType: soft From 45ac7769817b40ec5ce7f8827a66057c412def62 Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Fri, 7 Oct 2022 12:55:38 +0200 Subject: [PATCH 33/35] add docs string Signed-off-by: Kiss Miklos --- plugins/catalog-backend/src/ingestion/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 416fa968ab..78339b3b23 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -90,6 +90,7 @@ export type ScmLocationAnalyzer = { supports(url: string): boolean; /** This function can return an array of already existing entities */ analyze(options: AnalyzeOptions): Promise<{ + /** Existing entities in the analyzed location */ existing: AnalyzeLocationExistingEntity[]; }>; }; From 404366c8539d97c4b5215eb0b48d1ec0e709b2ea Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Fri, 7 Oct 2022 14:31:55 +0200 Subject: [PATCH 34/35] add changeset Signed-off-by: Kiss Miklos --- .changeset/brave-goats-rush.md | 5 +++++ plugins/catalog-common/package.json | 1 - yarn.lock | 1 - 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 .changeset/brave-goats-rush.md diff --git a/.changeset/brave-goats-rush.md b/.changeset/brave-goats-rush.md new file mode 100644 index 0000000000..12dae257af --- /dev/null +++ b/.changeset/brave-goats-rush.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-node': patch +--- + +Deprecated the `LocationSpec` type. It got moved from this package to the `@backstage/plugin-catalog-common` and will be removed from this after some time. diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 96e9f31b43..a847d5766c 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -34,7 +34,6 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", - "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-search-common": "workspace:^" }, diff --git a/yarn.lock b/yarn.lock index aca25a583e..8afb919f99 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4720,7 +4720,6 @@ __metadata: dependencies: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" - "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-search-common": "workspace:^" languageName: unknown From 9552527c37dfd48b410dc4f6c959a166ce7024ab Mon Sep 17 00:00:00 2001 From: Kiss Miklos Date: Fri, 7 Oct 2022 16:02:05 +0200 Subject: [PATCH 35/35] fix changeset Signed-off-by: Kiss Miklos --- .changeset/brave-goats-rush.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/brave-goats-rush.md b/.changeset/brave-goats-rush.md index 12dae257af..8a14f8ea00 100644 --- a/.changeset/brave-goats-rush.md +++ b/.changeset/brave-goats-rush.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-node': patch +'@backstage/plugin-catalog-node': minor --- -Deprecated the `LocationSpec` type. It got moved from this package to the `@backstage/plugin-catalog-common` and will be removed from this after some time. +Deprecated the `LocationSpec` type. It got moved from this package to the `@backstage/plugin-catalog-common` so make sure imports are updated.