@backstage/plugin-catalog-backend 1.26.1-next.0 → 1.26.2-next.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @backstage/plugin-catalog-backend
2
2
 
3
+ ## 1.26.2-next.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 62747f8: Fixed a bug where the concurrency limiter for URL reading was not honored
8
+ - 8206f49: Fix a bug where etags were expiring too soon in the URL reader
9
+ - Updated dependencies
10
+ - @backstage/integration@1.15.1-next.0
11
+ - @backstage/backend-openapi-utils@0.1.19-next.0
12
+ - @backstage/backend-plugin-api@1.0.1-next.0
13
+ - @backstage/catalog-client@1.7.0
14
+ - @backstage/catalog-model@1.7.0
15
+ - @backstage/config@1.2.0
16
+ - @backstage/errors@1.2.4
17
+ - @backstage/types@1.1.1
18
+ - @backstage/plugin-catalog-common@1.1.0
19
+ - @backstage/plugin-catalog-node@1.13.1-next.0
20
+ - @backstage/plugin-events-node@0.4.1-next.0
21
+ - @backstage/plugin-permission-common@0.8.1
22
+ - @backstage/plugin-permission-node@0.8.4-next.0
23
+ - @backstage/plugin-search-backend-module-catalog@0.2.3-next.1
24
+
3
25
  ## 1.26.1-next.0
4
26
 
5
27
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-backend__alpha",
3
- "version": "1.26.1-next.0",
3
+ "version": "1.26.2-next.1",
4
4
  "main": "../dist/alpha.cjs.js",
5
5
  "types": "../dist/alpha.d.ts"
6
6
  }
package/dist/alpha.cjs.js CHANGED
@@ -4,7 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var alpha = require('@backstage/plugin-catalog-common/alpha');
6
6
  var pluginPermissionNode = require('@backstage/plugin-permission-node');
7
- var CatalogBuilder = require('./cjs/CatalogBuilder-CGSl8LEN.cjs.js');
7
+ var CatalogBuilder = require('./cjs/CatalogBuilder-BBOOqsyi.cjs.js');
8
8
  var backendPluginApi = require('@backstage/backend-plugin-api');
9
9
  var pluginEventsNode = require('@backstage/plugin-events-node');
10
10
  var alpha$1 = require('@backstage/plugin-catalog-node/alpha');
@@ -14,10 +14,10 @@ require('@backstage/backend-common');
14
14
  require('@backstage/catalog-model');
15
15
  require('@backstage/integration');
16
16
  require('crypto');
17
- require('core-js/features/promise');
18
- require('codeowners-utils');
19
17
  require('git-url-parse');
20
18
  require('@backstage/plugin-catalog-node');
19
+ require('core-js/features/promise');
20
+ require('codeowners-utils');
21
21
  require('fs-extra');
22
22
  require('glob');
23
23
  require('path');
@@ -5,11 +5,11 @@ var catalogModel = require('@backstage/catalog-model');
5
5
  var integration = require('@backstage/integration');
6
6
  var crypto = require('crypto');
7
7
  var lodash = require('lodash');
8
+ var parseGitUrl = require('git-url-parse');
9
+ var pluginCatalogNode = require('@backstage/plugin-catalog-node');
8
10
  var errors = require('@backstage/errors');
9
11
  require('core-js/features/promise');
10
12
  var codeowners = require('codeowners-utils');
11
- var parseGitUrl = require('git-url-parse');
12
- var pluginCatalogNode = require('@backstage/plugin-catalog-node');
13
13
  var fs = require('fs-extra');
14
14
  var g = require('glob');
15
15
  var path = require('path');
@@ -56,8 +56,8 @@ function _interopNamespaceCompat(e) {
56
56
  }
57
57
 
58
58
  var lodash__default = /*#__PURE__*/_interopDefaultCompat(lodash);
59
- var codeowners__namespace = /*#__PURE__*/_interopNamespaceCompat(codeowners);
60
59
  var parseGitUrl__default = /*#__PURE__*/_interopDefaultCompat(parseGitUrl);
60
+ var codeowners__namespace = /*#__PURE__*/_interopNamespaceCompat(codeowners);
61
61
  var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
62
62
  var g__default = /*#__PURE__*/_interopDefaultCompat(g);
63
63
  var path__default = /*#__PURE__*/_interopDefaultCompat(path);
@@ -68,118 +68,6 @@ var uniq__default = /*#__PURE__*/_interopDefaultCompat(uniq);
68
68
  var splitToChunks__default = /*#__PURE__*/_interopDefaultCompat(splitToChunks);
69
69
  var yn__default = /*#__PURE__*/_interopDefaultCompat(yn);
70
70
 
71
- const USER_PATTERN = /^@.*/;
72
- const GROUP_PATTERN = /^@.*\/.*/;
73
- const EMAIL_PATTERN = /^.*@.*\..*$/;
74
- function resolveCodeOwner(contents, catalogInfoFileUrl) {
75
- const codeOwnerEntries = codeowners__namespace.parse(contents);
76
- const { filepath } = parseGitUrl__default.default(catalogInfoFileUrl);
77
- const match = codeowners__namespace.matchFile(filepath, codeOwnerEntries);
78
- return match ? normalizeCodeOwner(match.owners[0]) : void 0;
79
- }
80
- function normalizeCodeOwner(owner) {
81
- if (owner.match(GROUP_PATTERN)) {
82
- return owner.split("/")[1];
83
- } else if (owner.match(USER_PATTERN)) {
84
- return `User:${owner.substring(1)}`;
85
- } else if (owner.match(EMAIL_PATTERN)) {
86
- return owner.split("@")[0];
87
- }
88
- return owner;
89
- }
90
-
91
- const CODEOWNERS = "CODEOWNERS";
92
- const scmCodeOwnersPaths = {
93
- // https://mibexsoftware.atlassian.net/wiki/spaces/CODEOWNERS/pages/222822413/Usage
94
- bitbucket: [CODEOWNERS, `.bitbucket/${CODEOWNERS}`],
95
- // https://docs.gitlab.com/ee/user/project/code_owners.html#how-to-set-up-code-owners
96
- gitlab: [CODEOWNERS, `.gitlab/${CODEOWNERS}`, `docs/${CODEOWNERS}`],
97
- // https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-file-location
98
- github: [CODEOWNERS, `.github/${CODEOWNERS}`, `docs/${CODEOWNERS}`]
99
- };
100
-
101
- async function readCodeOwners(reader, sourceUrl, codeownersPaths) {
102
- const readOwnerLocation = async (path) => {
103
- const url = `${sourceUrl}${path}`;
104
- const data = await reader.readUrl(url);
105
- const buffer = await data.buffer();
106
- return buffer.toString();
107
- };
108
- const candidates = codeownersPaths.map(readOwnerLocation);
109
- return Promise.any(candidates).catch((aggregateError) => {
110
- const hardError = aggregateError.errors.find(
111
- (error) => !(error instanceof errors.NotFoundError)
112
- );
113
- if (hardError) {
114
- throw hardError;
115
- }
116
- return void 0;
117
- });
118
- }
119
- async function findCodeOwnerByTarget(reader, targetUrl, scmIntegration) {
120
- const codeownersPaths = scmCodeOwnersPaths[scmIntegration?.type ?? ""];
121
- const sourceUrl = scmIntegration?.resolveUrl({
122
- url: "/",
123
- base: targetUrl
124
- });
125
- if (!sourceUrl || !codeownersPaths) {
126
- return void 0;
127
- }
128
- const contents = await readCodeOwners(reader, sourceUrl, codeownersPaths);
129
- if (!contents) {
130
- return void 0;
131
- }
132
- const owner = resolveCodeOwner(contents, targetUrl);
133
- return owner;
134
- }
135
-
136
- const ALLOWED_KINDS = ["API", "Component", "Domain", "Resource", "System"];
137
- const ALLOWED_LOCATION_TYPES = ["url"];
138
- class CodeOwnersProcessor {
139
- integrations;
140
- logger;
141
- reader;
142
- static fromConfig(config, options) {
143
- const integrations = integration.ScmIntegrations.fromConfig(config);
144
- return new CodeOwnersProcessor({
145
- ...options,
146
- integrations
147
- });
148
- }
149
- constructor(options) {
150
- this.integrations = options.integrations;
151
- this.logger = options.logger;
152
- this.reader = options.reader;
153
- }
154
- getProcessorName() {
155
- return "CodeOwnersProcessor";
156
- }
157
- async preProcessEntity(entity, location) {
158
- if (!entity || !ALLOWED_KINDS.includes(entity.kind) || !ALLOWED_LOCATION_TYPES.includes(location.type) || entity.spec && entity.spec.owner) {
159
- return entity;
160
- }
161
- const scmIntegration = this.integrations.byUrl(location.target);
162
- if (!scmIntegration) {
163
- return entity;
164
- }
165
- const owner = await findCodeOwnerByTarget(
166
- this.reader,
167
- location.target,
168
- scmIntegration
169
- );
170
- if (!owner) {
171
- this.logger.debug(
172
- `CodeOwnerProcessor could not resolve owner for ${location.target}`
173
- );
174
- return entity;
175
- }
176
- return {
177
- ...entity,
178
- spec: { ...entity.spec, owner }
179
- };
180
- }
181
- }
182
-
183
71
  const commitHashRegExp = /\b[0-9a-f]{40,}\b/;
184
72
  class AnnotateLocationEntityProcessor {
185
73
  constructor(options) {
@@ -436,6 +324,118 @@ class BuiltinKindsEntityProcessor {
436
324
  }
437
325
  }
438
326
 
327
+ const USER_PATTERN = /^@.*/;
328
+ const GROUP_PATTERN = /^@.*\/.*/;
329
+ const EMAIL_PATTERN = /^.*@.*\..*$/;
330
+ function resolveCodeOwner(contents, catalogInfoFileUrl) {
331
+ const codeOwnerEntries = codeowners__namespace.parse(contents);
332
+ const { filepath } = parseGitUrl__default.default(catalogInfoFileUrl);
333
+ const match = codeowners__namespace.matchFile(filepath, codeOwnerEntries);
334
+ return match ? normalizeCodeOwner(match.owners[0]) : void 0;
335
+ }
336
+ function normalizeCodeOwner(owner) {
337
+ if (owner.match(GROUP_PATTERN)) {
338
+ return owner.split("/")[1];
339
+ } else if (owner.match(USER_PATTERN)) {
340
+ return `User:${owner.substring(1)}`;
341
+ } else if (owner.match(EMAIL_PATTERN)) {
342
+ return owner.split("@")[0];
343
+ }
344
+ return owner;
345
+ }
346
+
347
+ const CODEOWNERS = "CODEOWNERS";
348
+ const scmCodeOwnersPaths = {
349
+ // https://mibexsoftware.atlassian.net/wiki/spaces/CODEOWNERS/pages/222822413/Usage
350
+ bitbucket: [CODEOWNERS, `.bitbucket/${CODEOWNERS}`],
351
+ // https://docs.gitlab.com/ee/user/project/code_owners.html#how-to-set-up-code-owners
352
+ gitlab: [CODEOWNERS, `.gitlab/${CODEOWNERS}`, `docs/${CODEOWNERS}`],
353
+ // https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-file-location
354
+ github: [CODEOWNERS, `.github/${CODEOWNERS}`, `docs/${CODEOWNERS}`]
355
+ };
356
+
357
+ async function readCodeOwners(reader, sourceUrl, codeownersPaths) {
358
+ const readOwnerLocation = async (path) => {
359
+ const url = `${sourceUrl}${path}`;
360
+ const data = await reader.readUrl(url);
361
+ const buffer = await data.buffer();
362
+ return buffer.toString();
363
+ };
364
+ const candidates = codeownersPaths.map(readOwnerLocation);
365
+ return Promise.any(candidates).catch((aggregateError) => {
366
+ const hardError = aggregateError.errors.find(
367
+ (error) => !(error instanceof errors.NotFoundError)
368
+ );
369
+ if (hardError) {
370
+ throw hardError;
371
+ }
372
+ return void 0;
373
+ });
374
+ }
375
+ async function findCodeOwnerByTarget(reader, targetUrl, scmIntegration) {
376
+ const codeownersPaths = scmCodeOwnersPaths[scmIntegration?.type ?? ""];
377
+ const sourceUrl = scmIntegration?.resolveUrl({
378
+ url: "/",
379
+ base: targetUrl
380
+ });
381
+ if (!sourceUrl || !codeownersPaths) {
382
+ return void 0;
383
+ }
384
+ const contents = await readCodeOwners(reader, sourceUrl, codeownersPaths);
385
+ if (!contents) {
386
+ return void 0;
387
+ }
388
+ const owner = resolveCodeOwner(contents, targetUrl);
389
+ return owner;
390
+ }
391
+
392
+ const ALLOWED_KINDS = ["API", "Component", "Domain", "Resource", "System"];
393
+ const ALLOWED_LOCATION_TYPES = ["url"];
394
+ class CodeOwnersProcessor {
395
+ integrations;
396
+ logger;
397
+ reader;
398
+ static fromConfig(config, options) {
399
+ const integrations = integration.ScmIntegrations.fromConfig(config);
400
+ return new CodeOwnersProcessor({
401
+ ...options,
402
+ integrations
403
+ });
404
+ }
405
+ constructor(options) {
406
+ this.integrations = options.integrations;
407
+ this.logger = options.logger;
408
+ this.reader = options.reader;
409
+ }
410
+ getProcessorName() {
411
+ return "CodeOwnersProcessor";
412
+ }
413
+ async preProcessEntity(entity, location) {
414
+ if (!entity || !ALLOWED_KINDS.includes(entity.kind) || !ALLOWED_LOCATION_TYPES.includes(location.type) || entity.spec && entity.spec.owner) {
415
+ return entity;
416
+ }
417
+ const scmIntegration = this.integrations.byUrl(location.target);
418
+ if (!scmIntegration) {
419
+ return entity;
420
+ }
421
+ const owner = await findCodeOwnerByTarget(
422
+ this.reader,
423
+ location.target,
424
+ scmIntegration
425
+ );
426
+ if (!owner) {
427
+ this.logger.debug(
428
+ `CodeOwnerProcessor could not resolve owner for ${location.target}`
429
+ );
430
+ return entity;
431
+ }
432
+ return {
433
+ ...entity,
434
+ spec: { ...entity.spec, owner }
435
+ };
436
+ }
437
+ }
438
+
439
439
  const glob = util.promisify(g__default.default);
440
440
  const LOCATION_TYPE = "file";
441
441
  class FileReaderProcessor {
@@ -612,7 +612,11 @@ const CACHE_KEY = "v1";
612
612
  class UrlReaderProcessor {
613
613
  constructor(options) {
614
614
  this.options = options;
615
+ this.#limiter = limiterFactory__default.default(5);
615
616
  }
617
+ // This limiter is used for only consuming a limited number of read streams
618
+ // concurrently.
619
+ #limiter;
616
620
  getProcessorName() {
617
621
  return "url-reader";
618
622
  }
@@ -655,6 +659,7 @@ class UrlReaderProcessor {
655
659
  emit(parseResult);
656
660
  }
657
661
  emit(pluginCatalogNode.processingResult.refresh(`${location.type}:${location.target}`));
662
+ await cache.set(CACHE_KEY, cacheItem);
658
663
  } else if (error.name === "NotFoundError") {
659
664
  if (!optional) {
660
665
  emit(pluginCatalogNode.processingResult.notFoundError(location, message));
@@ -668,11 +673,10 @@ class UrlReaderProcessor {
668
673
  async doRead(location, etag) {
669
674
  const { filepath } = parseGitUrl__default.default(location);
670
675
  if (filepath?.match(/[*?]/)) {
671
- const limiter = limiterFactory__default.default(5);
672
676
  const response = await this.options.reader.search(location, { etag });
673
677
  const output = response.files.map(async (file) => ({
674
678
  url: file.url,
675
- data: await limiter(file.content)
679
+ data: await this.#limiter(file.content)
676
680
  }));
677
681
  return { response: await Promise.all(output), etag: response.etag };
678
682
  }
@@ -684,38 +688,6 @@ class UrlReaderProcessor {
684
688
  }
685
689
  }
686
690
 
687
- function* parseEntityYaml(data, location) {
688
- let documents;
689
- try {
690
- documents = yaml__default.default.parseAllDocuments(data.toString("utf8")).filter((d) => d);
691
- } catch (e) {
692
- const loc = catalogModel.stringifyLocationRef(location);
693
- const message = `Failed to parse YAML at ${loc}, ${e}`;
694
- yield pluginCatalogNode.processingResult.generalError(location, message);
695
- return;
696
- }
697
- for (const document of documents) {
698
- if (document.errors?.length) {
699
- const loc = catalogModel.stringifyLocationRef(location);
700
- const message = `YAML error at ${loc}, ${document.errors[0]}`;
701
- yield pluginCatalogNode.processingResult.generalError(location, message);
702
- } else {
703
- const json = document.toJSON();
704
- if (lodash__default.default.isPlainObject(json)) {
705
- yield pluginCatalogNode.processingResult.entity(location, json);
706
- } else if (json === null) ; else {
707
- const message = `Expected object at root, got ${typeof json}`;
708
- yield pluginCatalogNode.processingResult.generalError(location, message);
709
- }
710
- }
711
- }
712
- }
713
- const defaultEntityDataParser = async function* defaultEntityDataParser2({ data, location }) {
714
- for (const e of parseEntityYaml(data, location)) {
715
- yield e;
716
- }
717
- };
718
-
719
691
  function createRandomProcessingInterval(options) {
720
692
  const { minSeconds, maxSeconds } = options;
721
693
  return () => {
@@ -1072,6 +1044,38 @@ class AuthorizedLocationAnalyzer {
1072
1044
  }
1073
1045
  }
1074
1046
 
1047
+ function* parseEntityYaml(data, location) {
1048
+ let documents;
1049
+ try {
1050
+ documents = yaml__default.default.parseAllDocuments(data.toString("utf8")).filter((d) => d);
1051
+ } catch (e) {
1052
+ const loc = catalogModel.stringifyLocationRef(location);
1053
+ const message = `Failed to parse YAML at ${loc}, ${e}`;
1054
+ yield pluginCatalogNode.processingResult.generalError(location, message);
1055
+ return;
1056
+ }
1057
+ for (const document of documents) {
1058
+ if (document.errors?.length) {
1059
+ const loc = catalogModel.stringifyLocationRef(location);
1060
+ const message = `YAML error at ${loc}, ${document.errors[0]}`;
1061
+ yield pluginCatalogNode.processingResult.generalError(location, message);
1062
+ } else {
1063
+ const json = document.toJSON();
1064
+ if (lodash__default.default.isPlainObject(json)) {
1065
+ yield pluginCatalogNode.processingResult.entity(location, json);
1066
+ } else if (json === null) ; else {
1067
+ const message = `Expected object at root, got ${typeof json}`;
1068
+ yield pluginCatalogNode.processingResult.generalError(location, message);
1069
+ }
1070
+ }
1071
+ }
1072
+ }
1073
+ const defaultEntityDataParser = async function* defaultEntityDataParser2({ data, location }) {
1074
+ for (const e of parseEntityYaml(data, location)) {
1075
+ yield e;
1076
+ }
1077
+ };
1078
+
1075
1079
  function timestampToDateTime(input) {
1076
1080
  try {
1077
1081
  if (typeof input === "object") {
@@ -7372,4 +7376,4 @@ exports.createCatalogPermissionRule = createCatalogPermissionRule;
7372
7376
  exports.createRandomProcessingInterval = createRandomProcessingInterval;
7373
7377
  exports.parseEntityYaml = parseEntityYaml;
7374
7378
  exports.permissionRules = permissionRules;
7375
- //# sourceMappingURL=CatalogBuilder-CGSl8LEN.cjs.js.map
7379
+ //# sourceMappingURL=CatalogBuilder-BBOOqsyi.cjs.js.map