@red-hat-developer-hub/backstage-plugin-bulk-import-backend 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +115 -0
  2. package/README.md +161 -0
  3. package/app-config.yaml +0 -0
  4. package/config.d.ts +17 -0
  5. package/dist/catalog/catalogHttpClient.cjs.js +284 -0
  6. package/dist/catalog/catalogHttpClient.cjs.js.map +1 -0
  7. package/dist/catalog/catalogInfoGenerator.cjs.js +60 -0
  8. package/dist/catalog/catalogInfoGenerator.cjs.js.map +1 -0
  9. package/dist/catalog/catalogUtils.cjs.js +34 -0
  10. package/dist/catalog/catalogUtils.cjs.js.map +1 -0
  11. package/dist/generated/openapidocument.cjs.js +1168 -0
  12. package/dist/generated/openapidocument.cjs.js.map +1 -0
  13. package/dist/github/GithubAppManager.cjs.js +395 -0
  14. package/dist/github/GithubAppManager.cjs.js.map +1 -0
  15. package/dist/github/githubApiService.cjs.js +659 -0
  16. package/dist/github/githubApiService.cjs.js.map +1 -0
  17. package/dist/github/types.cjs.js +8 -0
  18. package/dist/github/types.cjs.js.map +1 -0
  19. package/dist/github/utils/ghUtils.cjs.js +103 -0
  20. package/dist/github/utils/ghUtils.cjs.js.map +1 -0
  21. package/dist/github/utils/orgUtils.cjs.js +151 -0
  22. package/dist/github/utils/orgUtils.cjs.js.map +1 -0
  23. package/dist/github/utils/prUtils.cjs.js +85 -0
  24. package/dist/github/utils/prUtils.cjs.js.map +1 -0
  25. package/dist/github/utils/repoUtils.cjs.js +307 -0
  26. package/dist/github/utils/repoUtils.cjs.js.map +1 -0
  27. package/dist/github/utils/utils.cjs.js +206 -0
  28. package/dist/github/utils/utils.cjs.js.map +1 -0
  29. package/dist/helpers/auditLogUtils.cjs.js +112 -0
  30. package/dist/helpers/auditLogUtils.cjs.js.map +1 -0
  31. package/dist/helpers/auth.cjs.js +36 -0
  32. package/dist/helpers/auth.cjs.js.map +1 -0
  33. package/dist/helpers/loggingUtils.cjs.js +19 -0
  34. package/dist/helpers/loggingUtils.cjs.js.map +1 -0
  35. package/dist/helpers/pagination.cjs.js +19 -0
  36. package/dist/helpers/pagination.cjs.js.map +1 -0
  37. package/dist/index.cjs.js +12 -0
  38. package/dist/index.cjs.js.map +1 -0
  39. package/dist/index.d.ts +25 -0
  40. package/dist/plugin.cjs.js +54 -0
  41. package/dist/plugin.cjs.js.map +1 -0
  42. package/dist/service/handlers/handlers.cjs.js +8 -0
  43. package/dist/service/handlers/handlers.cjs.js.map +1 -0
  44. package/dist/service/handlers/import/bulkImports.cjs.js +551 -0
  45. package/dist/service/handlers/import/bulkImports.cjs.js.map +1 -0
  46. package/dist/service/handlers/import/importStatus.cjs.js +49 -0
  47. package/dist/service/handlers/import/importStatus.cjs.js.map +1 -0
  48. package/dist/service/handlers/organization/organizations.cjs.js +77 -0
  49. package/dist/service/handlers/organization/organizations.cjs.js.map +1 -0
  50. package/dist/service/handlers/ping/ping.cjs.js +12 -0
  51. package/dist/service/handlers/ping/ping.cjs.js.map +1 -0
  52. package/dist/service/handlers/repository/repositories.cjs.js +99 -0
  53. package/dist/service/handlers/repository/repositories.cjs.js.map +1 -0
  54. package/dist/service/router.cjs.js +324 -0
  55. package/dist/service/router.cjs.js.map +1 -0
  56. package/package.json +110 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,115 @@
1
+ ### Dependencies
2
+
3
+ ## 5.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 07bf748: Migrated from [janus-idp/backstage-plugins](https://github.com/janus-idp/backstage-plugins).
8
+ - Updated dependencies [07bf748]
9
+ - @red-hat-developer-hub/backstage-plugin-bulk-import-common@1.3.1
10
+
11
+ ## 5.0.0
12
+
13
+ ### Minor Changes
14
+
15
+ - 8244f28: chore(deps): update to backstage 1.32
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies [8244f28]
20
+ - @red-hat-developer-hub/backstage-plugin-bulk-import-common@1.3.0
21
+ - @janus-idp/backstage-plugin-audit-log-node@1.7.0
22
+
23
+ ## 4.0.1
24
+
25
+ ### Patch Changes
26
+
27
+ - 7342e9b: chore: remove @janus-idp/cli dep and relink local packages
28
+
29
+ This update removes `@janus-idp/cli` from all plugins, as it’s no longer necessary. Additionally, packages are now correctly linked with a specified version.
30
+
31
+ ## 4.0.0
32
+
33
+ ### Minor Changes
34
+
35
+ - d9551ae: feat(deps): update to backstage 1.31
36
+
37
+ ### Patch Changes
38
+
39
+ - d9551ae: Change local package references to a `*`
40
+ - d9551ae: pin the @janus-idp/cli package
41
+ - d9551ae: upgrade to yarn v3
42
+ - d9551ae: Change the export-dynamic script to no longer use any flags and remove the tracking of the dist-dynamic folder
43
+ - Updated dependencies [d9551ae]
44
+ - Updated dependencies [d9551ae]
45
+ - Updated dependencies [d9551ae]
46
+ - @red-hat-developer-hub/backstage-plugin-bulk-import-common@1.2.0
47
+ - @janus-idp/backstage-plugin-audit-log-node@1.6.0
48
+
49
+ * **@janus-idp/cli:** upgraded to 1.15.2
50
+
51
+ ### Dependencies
52
+
53
+ - **@janus-idp/backstage-plugin-audit-log-node:** upgraded to 1.5.1
54
+
55
+ ### Dependencies
56
+
57
+ - **@red-hat-developer-hub/backstage-plugin-bulk-import-common:** upgraded to 1.1.1
58
+
59
+ ### Dependencies
60
+
61
+ - **@janus-idp/cli:** upgraded to 1.15.1
62
+
63
+ ### Dependencies
64
+
65
+ - **@janus-idp/cli:** upgraded to 1.15.0
66
+
67
+ ### Dependencies
68
+
69
+ - **@red-hat-developer-hub/backstage-plugin-bulk-import-common:** upgraded to 1.1.0
70
+ - **@janus-idp/cli:** upgraded to 1.14.0
71
+ - **@janus-idp/backstage-plugin-audit-log-node:** upgraded to 1.5.0
72
+
73
+ ### Dependencies
74
+
75
+ - **@janus-idp/cli:** upgraded to 1.13.2
76
+
77
+ ### Dependencies
78
+
79
+ - **@janus-idp/backstage-plugin-audit-log-node:** upgraded to 1.4.1
80
+
81
+ ### Dependencies
82
+
83
+ - **@red-hat-developer-hub/backstage-plugin-bulk-import-common:** upgraded to 1.0.1
84
+
85
+ ### Dependencies
86
+
87
+ - **@red-hat-developer-hub/backstage-plugin-bulk-import-common:** upgraded to 1.0.1
88
+
89
+ ### Dependencies
90
+
91
+ - **@red-hat-developer-hub/backstage-plugin-bulk-import-common:** upgraded to 1.0.1
92
+
93
+ ### Dependencies
94
+
95
+ - **@janus-idp/backstage-plugin-audit-log-node:** upgraded to 1.0.0
96
+ - **@red-hat-developer-hub/backstage-plugin-bulk-import-common:** upgraded to 1.0.0
97
+
98
+ ### Dependencies
99
+
100
+ - **@red-hat-developer-hub/backstage-plugin-bulk-import-common:** upgraded to 1.0.0
101
+ - **@janus-idp/cli:** upgraded to 1.0.0
102
+
103
+ ### Dependencies
104
+
105
+ - **@janus-idp/cli:** upgraded to 1.13.1
106
+
107
+ ## @red-hat-developer-hub/backstage-plugin-bulk-import-backend [0.2.0](https://github.com/janus-idp/backstage-plugins/compare/@red-hat-developer-hub/backstage-plugin-bulk-import-backend@0.1.0...@red-hat-developer-hub/backstage-plugin-bulk-import-backend@0.2.0) (2024-07-25)
108
+
109
+ ### Features
110
+
111
+ - **deps:** update to backstage 1.29 ([#1900](https://github.com/janus-idp/backstage-plugins/issues/1900)) ([f53677f](https://github.com/janus-idp/backstage-plugins/commit/f53677fb02d6df43a9de98c43a9f101a6db76802))
112
+
113
+ ### Dependencies
114
+
115
+ - **@janus-idp/cli:** upgraded to 1.13.0
package/README.md ADDED
@@ -0,0 +1,161 @@
1
+ # Bulk Import Backend Plugin
2
+
3
+ This is `bulk-import-backend` plugin which provides Rest API to bulk import catalog entities into the catalog
4
+
5
+ ## For administrators
6
+
7
+ ### Installation and Configuration
8
+
9
+ To set up the bulk import backend package for the backend:
10
+
11
+ 1. Install the bulk import backend plugin using the following command:
12
+
13
+ ```console
14
+ yarn workspace backend add @red-hat-developer-hub/backstage-plugin-bulk-import-backend
15
+ ```
16
+
17
+ 1. Add the following code to the `packages/backend/src/index.ts` file:
18
+
19
+ ```ts title="packages/backend/src/index.ts"
20
+ const backend = createBackend();
21
+ /* highlight-add-next-line */
22
+ backend.add(
23
+ import('@red-hat-developer-hub/backstage-plugin-bulk-import-backend'),
24
+ );
25
+
26
+ backend.start();
27
+ ```
28
+
29
+ #### Permission Framework Support
30
+
31
+ The Bulk Import Backend plugin has support for the permission framework. A basic example permission policy is shown below to disallow access to the bulk import API for all users except those in the `backstage-admins` group.
32
+
33
+ 1. Create a backend module for the permission policy, under a `packages/backend/src/plugins/permissions.ts` file:
34
+
35
+ ```ts title="packages/backend/src/plugins/permissions.ts"
36
+ import { createBackendModule } from '@backstage/backend-plugin-api';
37
+ import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
38
+ import {
39
+ AuthorizeResult,
40
+ isPermission,
41
+ PolicyDecision,
42
+ } from '@backstage/plugin-permission-common';
43
+ import {
44
+ PermissionPolicy,
45
+ PolicyQuery,
46
+ } from '@backstage/plugin-permission-node';
47
+ import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha';
48
+
49
+ import { bulkImportPermission } from '@red-hat-developer-hub/backstage-plugin-bulk-import-common';
50
+
51
+ class BulkImportPermissionPolicy implements PermissionPolicy {
52
+ async handle(
53
+ request: PolicyQuery,
54
+ user?: BackstageIdentityResponse,
55
+ ): Promise<PolicyDecision> {
56
+ if (isPermission(request.permission, bulkImportPermission)) {
57
+ if (
58
+ user?.identity.ownershipEntityRefs.includes(
59
+ 'group:default/backstage-admins',
60
+ )
61
+ ) {
62
+ return { result: AuthorizeResult.ALLOW };
63
+ }
64
+ }
65
+ return { result: AuthorizeResult.DENY };
66
+ }
67
+ }
68
+
69
+ export const BulkImportPermissionBackendModule = createBackendModule({
70
+ pluginId: 'permission',
71
+ moduleId: 'custom-policy',
72
+ register(reg) {
73
+ reg.registerInit({
74
+ deps: { policy: policyExtensionPoint },
75
+ async init({ policy }) {
76
+ policy.setPolicy(new BulkImportPermissionPolicy());
77
+ },
78
+ });
79
+ },
80
+ });
81
+ ```
82
+
83
+ 2. Import `@backstage/plugin-permission-backend/alpha` and add your permission module to the `packages/backend/src/index.ts` file:
84
+
85
+ ```ts title="packages/backend/src/index.ts"
86
+ import { BulkImportPermissionBackendModule } from './plugins/permissions';
87
+
88
+ backend.add(BulkImportPermissionBackendModule);
89
+ backend.add(import('@backstage/plugin-permission-backend/alpha'));
90
+ ```
91
+
92
+ ### Audit Logging
93
+
94
+ Audit logging is backed by the [`backstage-plugin-audit-log-node`](https://www.npmjs.com/package/@janus-idp/backstage-plugin-audit-log-node) package. The Bulk Import Backend plugin adds the following events to the backend audit logs:
95
+
96
+ - **BulkImportUnknownEndpoint**: tracks requests to unknown endpoints.
97
+
98
+ - **BulkImportPing**: tracks `GET` requests to the `/ping` endpoint, which allows to make sure the bulk import backend is up and running.
99
+
100
+ - **BulkImportFindAllOrganizations**: tracks `GET` requests to the `/organizations` endpoint, which returns the list of organizations accessible from all configured GitHub Integrations.
101
+
102
+ - **BulkImportFindRepositoriesByOrganization**: tracks `GET` requests to the `/organizations/:orgName/repositories` endpoint, which returns the list of repositories for the specified organization (accessible from any of the configured GitHub Integrations).
103
+
104
+ - **BulkImportFindAllRepositories**: tracks `GET` requests to the `/repositories` endpoint, which returns the list of repositories accessible from all configured GitHub Integrations.
105
+
106
+ - **BulkImportFindAllImports**: tracks `GET` requests to the `/imports` endpoint, which returns the list of existing import jobs along with their statuses.
107
+
108
+ - **BulkImportCreateImportJobs**: tracks `POST` requests to the `/imports` endpoint, which allows to submit requests to bulk-import one or many repositories into the Backstage Catalog, by eventually creating import Pull Requests in the target repositories.
109
+
110
+ - **BulkImportFindImportStatusByRepo**: tracks `GET` requests to the `/import/by-repo` endpoint, which fetches details about the import job for the specified repository.
111
+
112
+ - **BulkImportDeleteImportByRepo**: tracks `DELETE` requests to the `/import/by-repo` endpoint, which deletes any existing import job for the specified repository, by closing any open import Pull Request that could have been created.
113
+
114
+ Example:
115
+
116
+ ```json
117
+ {
118
+ "actor": {
119
+ "actorId": "user:default/myuser",
120
+ "hostname": "localhost",
121
+ "ip": "::1",
122
+ "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
123
+ },
124
+ "eventName": "BulkImportFindAllOrganizations",
125
+ "isAuditLog": true,
126
+ "level": "info",
127
+ "message": "'get /organizations' endpoint hit by user:default/myuser",
128
+ "meta": {},
129
+ "plugin": "bulk-import",
130
+ "request": {
131
+ "body": {},
132
+ "method": "GET",
133
+ "params": {},
134
+ "query": {
135
+ "pagePerIntegration": "1",
136
+ "sizePerIntegration": "5"
137
+ },
138
+ "url": "/api/bulk-import/organizations?pagePerIntegration=1&sizePerIntegration=5"
139
+ },
140
+ "response": {
141
+ "status": 200
142
+ },
143
+ "service": "backstage",
144
+ "stage": "completion",
145
+ "status": "succeeded",
146
+ "timestamp": "2024-08-26 16:41:02"
147
+ }
148
+ ```
149
+
150
+ ## For Users
151
+
152
+ ### Usage
153
+
154
+ The bulk import backend plugin provides a REST API to bulk import catalog entities into the catalog. The API is available at the `/api/bulk-import` endpoint.
155
+
156
+ As a prerequisite, you need to add at least one GitHub Integration (using either a GitHub token or a GitHub App or both) in your app-config YAML file (or a local `app-config.local.yaml` file).
157
+ See https://backstage.io/docs/integrations/github/locations/#configuration and https://backstage.io/docs/integrations/github/github-apps/#including-in-integrations-config for more details.
158
+
159
+ ## REST API
160
+
161
+ Please refer to [`src/schema/openapi.yaml`](src/schema/openapi.yaml) for the API definition (along with some examples) and the [generated documentation](api-docs/README.md) for more details about the request and response parameters and formats.
File without changes
package/config.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ /*
2
+ * Copyright 2024 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ export interface Config {}
@@ -0,0 +1,284 @@
1
+ 'use strict';
2
+
3
+ var fetch = require('node-fetch');
4
+ var auth = require('../helpers/auth.cjs.js');
5
+ var loggingUtils = require('../helpers/loggingUtils.cjs.js');
6
+ var catalogUtils = require('./catalogUtils.cjs.js');
7
+
8
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
9
+
10
+ var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
11
+
12
+ class CatalogHttpClient {
13
+ logger;
14
+ config;
15
+ discovery;
16
+ auth;
17
+ catalogApi;
18
+ constructor(deps) {
19
+ this.logger = deps.logger;
20
+ this.config = deps.config;
21
+ this.discovery = deps.discovery;
22
+ this.auth = deps.auth;
23
+ this.catalogApi = deps.catalogApi;
24
+ }
25
+ // Wrapper for https://backstage.io/docs/features/software-catalog/software-catalog-api/#post-analyze-location
26
+ async analyzeLocation(repoUrl) {
27
+ this.logger.debug(`Forwarding request to analyze location: ${repoUrl}`);
28
+ const response = await fetch__default.default(
29
+ `${await this.discovery.getBaseUrl("catalog")}/analyze-location`,
30
+ {
31
+ headers: {
32
+ "Content-Type": "application/json",
33
+ Authorization: `Bearer ${await auth.getTokenForPlugin(
34
+ this.auth,
35
+ "catalog"
36
+ )}`
37
+ },
38
+ method: "POST",
39
+ body: JSON.stringify({
40
+ location: {
41
+ type: "github",
42
+ target: repoUrl
43
+ }
44
+ })
45
+ }
46
+ );
47
+ return (await response.json()).generateEntities ?? [];
48
+ }
49
+ async listCatalogUrlLocations(search, pageNumber, pageSize) {
50
+ const byId = await this.listCatalogUrlLocationsById(
51
+ search,
52
+ pageNumber,
53
+ pageSize
54
+ );
55
+ const result = /* @__PURE__ */ new Set();
56
+ for (const l of byId.locations) {
57
+ result.add(l.target);
58
+ }
59
+ return {
60
+ targetUrls: Array.from(result.values()),
61
+ totalCount: byId.totalCount
62
+ };
63
+ }
64
+ async listCatalogUrlLocationsById(search, pageNumber, pageSize) {
65
+ const result = await Promise.all([
66
+ this.listCatalogUrlLocationsFromConfig(search),
67
+ this.listCatalogUrlLocationsByIdFromLocationsEndpoint(search),
68
+ this.listCatalogUrlLocationEntitiesById(search, pageNumber, pageSize)
69
+ ]);
70
+ const locations = result.flatMap((u) => u.locations);
71
+ const totalCount = result.map((l) => l.totalCount ?? 0).reduce((accumulator, currentValue) => accumulator + currentValue, 0);
72
+ return {
73
+ locations,
74
+ totalCount
75
+ };
76
+ }
77
+ async listCatalogUrlLocationsByIdFromLocationsEndpoint(search) {
78
+ const url = `${await this.discovery.getBaseUrl("catalog")}/locations`;
79
+ const response = await fetch__default.default(url, {
80
+ headers: {
81
+ Accept: "application/json",
82
+ Authorization: `Bearer ${await auth.getTokenForPlugin(
83
+ this.auth,
84
+ "catalog"
85
+ )}`
86
+ },
87
+ method: "GET"
88
+ });
89
+ const locations = await response.json();
90
+ if (!Array.isArray(locations)) {
91
+ return { locations: [] };
92
+ }
93
+ const res = locations.filter(
94
+ (location) => location.data?.target && location.data?.type === "url"
95
+ ).map((location) => {
96
+ return {
97
+ id: location.data?.id,
98
+ target: location.data.target
99
+ };
100
+ });
101
+ const filtered = catalogUtils.filterLocations(res, search);
102
+ return { locations: filtered, totalCount: filtered.length };
103
+ }
104
+ listCatalogUrlLocationsFromConfig(search) {
105
+ const locationConfigs = this.config.getOptionalConfigArray("catalog.locations") ?? [];
106
+ const res = locationConfigs.filter(
107
+ (location) => location.getOptionalString("target") && location.getOptionalString("type") === "url"
108
+ ).map((location) => {
109
+ const target = location.getString("target");
110
+ return {
111
+ id: `app-config-location--${target}`,
112
+ target
113
+ };
114
+ });
115
+ const filtered = catalogUtils.filterLocations(res, search);
116
+ return { locations: filtered, totalCount: filtered.length };
117
+ }
118
+ async listCatalogUrlLocationEntitiesById(search, _pageNumber, _pageSize) {
119
+ const result = await this.catalogApi.getEntities(
120
+ {
121
+ filter: {
122
+ kind: "Location"
123
+ },
124
+ // There is no query parameter to find entities with target URLs containing a string.
125
+ // The existing filter does an exact matching. That's why we are retrieving this hard-coded high number of Locations.
126
+ limit: 9999,
127
+ offset: 0,
128
+ order: { field: "metadata.name", order: "desc" }
129
+ },
130
+ {
131
+ token: await auth.getTokenForPlugin(this.auth, "catalog")
132
+ }
133
+ );
134
+ const locations = result?.items ?? [];
135
+ const res = locations.filter(
136
+ (location) => location.spec?.target && location.spec?.type === "url"
137
+ ).map((location) => {
138
+ return {
139
+ id: location.metadata.uid,
140
+ target: location.spec.target
141
+ };
142
+ });
143
+ const filtered = catalogUtils.filterLocations(res, search);
144
+ return { locations: filtered, totalCount: filtered.length };
145
+ }
146
+ /**
147
+ * verifyLocationExistence checks for the existence of the Location target.
148
+ * Under the hood, it attempts to read the target URL and will return false if the target could not be found
149
+ * and even if there is already a Location row in the database.
150
+ * @param repoCatalogUrl
151
+ */
152
+ async verifyLocationExistence(repoCatalogUrl) {
153
+ try {
154
+ const result = await this.catalogApi.addLocation(
155
+ {
156
+ type: "url",
157
+ target: repoCatalogUrl,
158
+ dryRun: true
159
+ },
160
+ {
161
+ token: await auth.getTokenForPlugin(this.auth, "catalog")
162
+ }
163
+ );
164
+ return result.exists;
165
+ } catch (error) {
166
+ if (error.message?.includes("NotFoundError")) {
167
+ return false;
168
+ }
169
+ throw error;
170
+ }
171
+ }
172
+ async hasEntityInCatalog(entityName) {
173
+ return this.catalogApi.queryEntities(
174
+ {
175
+ filter: {
176
+ "metadata.name": entityName
177
+ },
178
+ limit: 1
179
+ },
180
+ {
181
+ token: await auth.getTokenForPlugin(this.auth, "catalog")
182
+ }
183
+ ).then((resp) => resp.items?.length > 0);
184
+ }
185
+ async possiblyCreateLocation(repoCatalogUrl) {
186
+ try {
187
+ await this.catalogApi.addLocation(
188
+ {
189
+ type: "url",
190
+ target: repoCatalogUrl
191
+ },
192
+ {
193
+ token: await auth.getTokenForPlugin(this.auth, "catalog")
194
+ }
195
+ );
196
+ } catch (error) {
197
+ if (!error.message?.includes("ConflictError")) {
198
+ throw error;
199
+ }
200
+ }
201
+ }
202
+ async deleteCatalogLocationById(locationId) {
203
+ try {
204
+ const url = `${await this.discovery.getBaseUrl(
205
+ "catalog"
206
+ )}/locations/${locationId}`;
207
+ await fetch__default.default(url, {
208
+ headers: {
209
+ Accept: "application/json",
210
+ Authorization: `Bearer ${await auth.getTokenForPlugin(
211
+ this.auth,
212
+ "catalog"
213
+ )}`
214
+ },
215
+ method: "DELETE"
216
+ });
217
+ } catch (error) {
218
+ loggingUtils.logErrorIfNeeded(
219
+ this.logger,
220
+ `Could not delete location ${locationId}`,
221
+ error
222
+ );
223
+ }
224
+ }
225
+ async deleteCatalogLocationEntityById(locationUid) {
226
+ await this.catalogApi.removeEntityByUid(locationUid, {
227
+ token: await auth.getTokenForPlugin(this.auth, "catalog")
228
+ });
229
+ }
230
+ async findLocationEntitiesByRepoUrl(repoUrl, defaultBranch) {
231
+ return this.findLocationEntitiesByTargetUrl(
232
+ catalogUtils.getCatalogUrl(this.config, repoUrl, defaultBranch)
233
+ );
234
+ }
235
+ async findLocationEntitiesByTargetUrl(targetUrl, limit) {
236
+ return this.catalogApi.queryEntities(
237
+ {
238
+ filter: [
239
+ { kind: "Location", "spec.type": "url", "spec.target": targetUrl }
240
+ ],
241
+ fields: ["metadata.namespace", "metadata.name", "metadata.uid"],
242
+ limit
243
+ },
244
+ {
245
+ token: await auth.getTokenForPlugin(this.auth, "catalog")
246
+ }
247
+ ).then((resp) => resp.items);
248
+ }
249
+ async refreshLocationByRepoUrl(repoUrl, defaultBranch) {
250
+ const promises = [];
251
+ this.findLocationEntitiesByRepoUrl(repoUrl, defaultBranch).then(
252
+ (entities) => {
253
+ const nbEntities = entities.length;
254
+ if (nbEntities === 0) {
255
+ this.logger.debug(`No Location Entity found for repo: ${repoUrl}`);
256
+ return;
257
+ }
258
+ this.logger.debug(
259
+ `Refreshing ${nbEntities} Location(s) for repo: ${repoUrl}`
260
+ );
261
+ entities.forEach(
262
+ (ent) => promises.push(
263
+ this.refreshEntity(
264
+ "location",
265
+ ent.metadata.name,
266
+ ent.metadata.namespace
267
+ )
268
+ )
269
+ );
270
+ }
271
+ );
272
+ await Promise.all(promises);
273
+ }
274
+ async refreshEntity(kind, name, namespace = "default") {
275
+ const entityRef = `${kind}:${namespace}/${name}`;
276
+ this.logger.debug(`Refreshing entityRef: ${entityRef}`);
277
+ await this.catalogApi.refreshEntity(entityRef, {
278
+ token: await auth.getTokenForPlugin(this.auth, "catalog")
279
+ });
280
+ }
281
+ }
282
+
283
+ exports.CatalogHttpClient = CatalogHttpClient;
284
+ //# sourceMappingURL=catalogHttpClient.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalogHttpClient.cjs.js","sources":["../../src/catalog/catalogHttpClient.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n AuthService,\n DiscoveryService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport type { CatalogApi } from '@backstage/catalog-client';\nimport type { LocationEntity } from '@backstage/catalog-model';\nimport type { Config } from '@backstage/config';\n\nimport fetch from 'node-fetch';\n\nimport { getTokenForPlugin, logErrorIfNeeded } from '../helpers';\nimport { filterLocations, getCatalogUrl } from './catalogUtils';\n\nexport class CatalogHttpClient {\n private readonly logger: LoggerService;\n private readonly config: Config;\n private readonly discovery: DiscoveryService;\n private readonly auth: AuthService;\n private readonly catalogApi: CatalogApi;\n\n constructor(deps: {\n logger: LoggerService;\n config: Config;\n discovery: DiscoveryService;\n auth: AuthService;\n catalogApi: CatalogApi;\n }) {\n this.logger = deps.logger;\n this.config = deps.config;\n this.discovery = deps.discovery;\n this.auth = deps.auth;\n this.catalogApi = deps.catalogApi;\n }\n\n // Wrapper for https://backstage.io/docs/features/software-catalog/software-catalog-api/#post-analyze-location\n async analyzeLocation(repoUrl: string): Promise<any[]> {\n this.logger.debug(`Forwarding request to analyze location: ${repoUrl}`);\n const response = await fetch(\n `${await this.discovery.getBaseUrl('catalog')}/analyze-location`,\n {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${await getTokenForPlugin(\n this.auth,\n 'catalog',\n )}`,\n },\n method: 'POST',\n body: JSON.stringify({\n location: {\n type: 'github',\n target: repoUrl,\n },\n }),\n },\n );\n return (await response.json()).generateEntities ?? [];\n }\n\n async listCatalogUrlLocations(\n search?: string,\n pageNumber?: number,\n pageSize?: number,\n ): Promise<{ targetUrls: string[]; totalCount?: number }> {\n const byId = await this.listCatalogUrlLocationsById(\n search,\n pageNumber,\n pageSize,\n );\n const result = new Set<string>();\n for (const l of byId.locations) {\n result.add(l.target);\n }\n return {\n targetUrls: Array.from(result.values()),\n totalCount: byId.totalCount,\n };\n }\n\n async listCatalogUrlLocationsById(\n search?: string,\n pageNumber?: number,\n pageSize?: number,\n ): Promise<{\n locations: { id?: string; target: string }[];\n totalCount?: number;\n }> {\n const result = await Promise.all([\n this.listCatalogUrlLocationsFromConfig(search),\n this.listCatalogUrlLocationsByIdFromLocationsEndpoint(search),\n this.listCatalogUrlLocationEntitiesById(search, pageNumber, pageSize),\n ]);\n const locations = result.flatMap(u => u.locations);\n // we might have duplicate elements here\n const totalCount = result\n .map(l => l.totalCount ?? 0)\n .reduce((accumulator, currentValue) => accumulator + currentValue, 0);\n return {\n locations,\n totalCount,\n };\n }\n\n async listCatalogUrlLocationsByIdFromLocationsEndpoint(\n search?: string,\n ): Promise<{\n locations: { id?: string; target: string }[];\n totalCount?: number;\n }> {\n const url = `${await this.discovery.getBaseUrl('catalog')}/locations`;\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n Authorization: `Bearer ${await getTokenForPlugin(\n this.auth,\n 'catalog',\n )}`,\n },\n method: 'GET',\n });\n const locations = (await response.json()) as {\n data: { id: string; target: string; type: string };\n }[];\n if (!Array.isArray(locations)) {\n return { locations: [] };\n }\n const res = locations\n .filter(\n location => location.data?.target && location.data?.type === 'url',\n )\n .map(location => {\n return {\n id: location.data?.id,\n target: location.data.target,\n };\n });\n const filtered = filterLocations(res, search);\n return { locations: filtered, totalCount: filtered.length };\n }\n\n listCatalogUrlLocationsFromConfig(search?: string): {\n locations: { id?: string; target: string }[];\n totalCount?: number;\n } {\n const locationConfigs =\n this.config.getOptionalConfigArray('catalog.locations') ?? [];\n const res = locationConfigs\n .filter(\n location =>\n location.getOptionalString('target') &&\n location.getOptionalString('type') === 'url',\n )\n .map(location => {\n const target = location.getString('target');\n return {\n id: `app-config-location--${target}`,\n target,\n };\n });\n const filtered = filterLocations(res, search);\n return { locations: filtered, totalCount: filtered.length };\n }\n\n async listCatalogUrlLocationEntitiesById(\n search?: string,\n _pageNumber?: number,\n _pageSize?: number,\n ): Promise<{\n locations: { id?: string; target: string }[];\n totalCount?: number;\n }> {\n const result = await this.catalogApi.getEntities(\n {\n filter: {\n kind: 'Location',\n },\n // There is no query parameter to find entities with target URLs containing a string.\n // The existing filter does an exact matching. That's why we are retrieving this hard-coded high number of Locations.\n limit: 9999,\n offset: 0,\n order: { field: 'metadata.name', order: 'desc' },\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n );\n const locations = (result?.items ?? []) as LocationEntity[];\n const res = locations\n .filter(\n location => location.spec?.target && location.spec?.type === 'url',\n )\n .map(location => {\n return {\n id: location.metadata.uid,\n target: location.spec.target!,\n };\n });\n const filtered = filterLocations(res, search);\n return { locations: filtered, totalCount: filtered.length };\n }\n\n /**\n * verifyLocationExistence checks for the existence of the Location target.\n * Under the hood, it attempts to read the target URL and will return false if the target could not be found\n * and even if there is already a Location row in the database.\n * @param repoCatalogUrl\n */\n async verifyLocationExistence(repoCatalogUrl: string): Promise<boolean> {\n try {\n const result = await this.catalogApi.addLocation(\n {\n type: 'url',\n target: repoCatalogUrl,\n dryRun: true,\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n );\n // The `result.exists` field is only filled in dryRun mode\n return result.exists as boolean;\n } catch (error: any) {\n if (error.message?.includes('NotFoundError')) {\n return false;\n }\n throw error;\n }\n }\n\n async hasEntityInCatalog(entityName: string) {\n return this.catalogApi\n .queryEntities(\n {\n filter: {\n 'metadata.name': entityName,\n },\n limit: 1,\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n )\n .then(resp => resp.items?.length > 0);\n }\n\n async possiblyCreateLocation(repoCatalogUrl: string) {\n try {\n await this.catalogApi.addLocation(\n {\n type: 'url',\n target: repoCatalogUrl,\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n );\n } catch (error: any) {\n if (!error.message?.includes('ConflictError')) {\n throw error;\n }\n // Location already exists, which is fine\n }\n }\n\n async deleteCatalogLocationById(locationId: string): Promise<void> {\n try {\n const url = `${await this.discovery.getBaseUrl(\n 'catalog',\n )}/locations/${locationId}`;\n await fetch(url, {\n headers: {\n Accept: 'application/json',\n Authorization: `Bearer ${await getTokenForPlugin(\n this.auth,\n 'catalog',\n )}`,\n },\n method: 'DELETE',\n });\n } catch (error: any) {\n logErrorIfNeeded(\n this.logger,\n `Could not delete location ${locationId}`,\n error,\n );\n }\n }\n\n async deleteCatalogLocationEntityById(locationUid: string): Promise<void> {\n await this.catalogApi.removeEntityByUid(locationUid, {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n });\n }\n\n async findLocationEntitiesByRepoUrl(repoUrl: string, defaultBranch?: string) {\n return this.findLocationEntitiesByTargetUrl(\n getCatalogUrl(this.config, repoUrl, defaultBranch),\n );\n }\n\n async findLocationEntitiesByTargetUrl(targetUrl: string, limit?: number) {\n return this.catalogApi\n .queryEntities(\n {\n filter: [\n { kind: 'Location', 'spec.type': 'url', 'spec.target': targetUrl },\n ],\n fields: ['metadata.namespace', 'metadata.name', 'metadata.uid'],\n limit,\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n )\n .then(resp => resp.items);\n }\n\n async refreshLocationByRepoUrl(repoUrl: string, defaultBranch?: string) {\n const promises: Promise<void>[] = [];\n this.findLocationEntitiesByRepoUrl(repoUrl, defaultBranch).then(\n entities => {\n const nbEntities = entities.length;\n if (nbEntities === 0) {\n this.logger.debug(`No Location Entity found for repo: ${repoUrl}`);\n return;\n }\n this.logger.debug(\n `Refreshing ${nbEntities} Location(s) for repo: ${repoUrl}`,\n );\n entities.forEach(ent =>\n promises.push(\n this.refreshEntity(\n 'location',\n ent.metadata.name,\n ent.metadata.namespace,\n ),\n ),\n );\n },\n );\n await Promise.all(promises);\n }\n\n async refreshEntity(\n kind: string,\n name: string,\n namespace: string = 'default',\n ) {\n const entityRef = `${kind}:${namespace}/${name}`;\n this.logger.debug(`Refreshing entityRef: ${entityRef}`);\n await this.catalogApi.refreshEntity(entityRef, {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n });\n }\n}\n"],"names":["fetch","getTokenForPlugin","filterLocations","logErrorIfNeeded","getCatalogUrl"],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ var gitUrlParse = require('git-url-parse');
4
+ var jsYaml = require('js-yaml');
5
+ require('@backstage/errors');
6
+ require('@backstage/plugin-permission-common');
7
+ require('@red-hat-developer-hub/backstage-plugin-bulk-import-common');
8
+ var loggingUtils = require('../helpers/loggingUtils.cjs.js');
9
+
10
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
11
+
12
+ var gitUrlParse__default = /*#__PURE__*/_interopDefaultCompat(gitUrlParse);
13
+ var jsYaml__default = /*#__PURE__*/_interopDefaultCompat(jsYaml);
14
+
15
+ class CatalogInfoGenerator {
16
+ logger;
17
+ catalogHttpClient;
18
+ constructor(logger, catalogHttpClient) {
19
+ this.logger = logger;
20
+ this.catalogHttpClient = catalogHttpClient;
21
+ }
22
+ async generateDefaultCatalogInfoContent(repoUrl, analyzeLocation = true) {
23
+ const gitUrl = gitUrlParse__default.default(repoUrl);
24
+ const defaultCatalogInfo = `---
25
+ apiVersion: backstage.io/v1alpha1
26
+ kind: Component
27
+ metadata:
28
+ name: ${gitUrl.name}
29
+ annotations:
30
+ github.com/project-slug: ${gitUrl.organization}/${gitUrl.name}
31
+ spec:
32
+ type: other
33
+ lifecycle: unknown
34
+ owner: ${gitUrl.organization}
35
+ ---`;
36
+ if (!analyzeLocation) {
37
+ return defaultCatalogInfo;
38
+ }
39
+ let generatedEntities = [];
40
+ try {
41
+ generatedEntities = await this.catalogHttpClient.analyzeLocation(repoUrl);
42
+ } catch (error) {
43
+ loggingUtils.logErrorIfNeeded(
44
+ this.logger,
45
+ `Could not analyze location ${repoUrl}`,
46
+ error
47
+ );
48
+ }
49
+ if (generatedEntities.length === 0) {
50
+ return defaultCatalogInfo;
51
+ }
52
+ return generatedEntities.map(
53
+ (generatedEntity) => `---
54
+ ${jsYaml__default.default.dump(generatedEntity.entity)}`
55
+ ).join("\n");
56
+ }
57
+ }
58
+
59
+ exports.CatalogInfoGenerator = CatalogInfoGenerator;
60
+ //# sourceMappingURL=catalogInfoGenerator.cjs.js.map