@backstage/plugin-catalog-backend 0.21.3 → 0.21.4

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,45 @@
1
1
  # @backstage/plugin-catalog-backend
2
2
 
3
+ ## 0.21.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 379da9fb1d: The following processors now properly accept an `ScmIntegrationRegistry` (an
8
+ interface) instead of an `ScmIntegrations` (which is a concrete class).
9
+
10
+ - `AzureDevOpsDiscoveryProcessor`
11
+ - `CodeOwnersProcessor`
12
+ - `GitLabDiscoveryProcessor`
13
+ - `GithubDiscoveryProcessor`
14
+ - `GithubMultiOrgReaderProcessor`
15
+ - `GithubOrgReaderProcessor`
16
+
17
+ - 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5
18
+ - c77c5c7eb6: Added `backstage.role` to `package.json`
19
+ - 538ca90790: Use updated type names from `@backstage/catalog-client`
20
+ - ca1d6c1788: Support "dependencyOf" relation in Resource entities
21
+ - 244d24ebc4: Import `Location` from the `@backstage/catalog-client` package.
22
+ - e483dd6c72: Update internal `Location` validation.
23
+ - 216725b434: Updated to use new names for `parseLocationRef` and `stringifyLocationRef`
24
+ - e72d371296: Use `TemplateEntityV1beta2` from `@backstage/plugin-scaffolder-common` instead
25
+ of `@backstage/catalog-model`.
26
+ - 27eccab216: Replaces use of deprecated catalog-model constants.
27
+ - 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`.
28
+ - b590e9b58d: Optimized entity provider mutations with large numbers of new additions, such as big initial startup commits
29
+ - Updated dependencies
30
+ - @backstage/plugin-scaffolder-common@0.2.0
31
+ - @backstage/backend-common@0.10.8
32
+ - @backstage/catalog-client@0.7.0
33
+ - @backstage/errors@0.2.1
34
+ - @backstage/integration@0.7.3
35
+ - @backstage/plugin-permission-common@0.5.0
36
+ - @backstage/catalog-model@0.10.0
37
+ - @backstage/config@0.1.14
38
+ - @backstage/search-common@0.2.3
39
+ - @backstage/types@0.1.2
40
+ - @backstage/plugin-catalog-common@0.1.3
41
+ - @backstage/plugin-permission-node@0.5.0
42
+
3
43
  ## 0.21.3
4
44
 
5
45
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -11,6 +11,7 @@ var parseGitUrl = require('git-url-parse');
11
11
  var AWS = require('aws-sdk');
12
12
  var limiterFactory = require('p-limit');
13
13
  var fetch = require('node-fetch');
14
+ var pluginScaffolderCommon = require('@backstage/plugin-scaffolder-common');
14
15
  require('core-js/features/promise');
15
16
  var codeowners = require('codeowners-utils');
16
17
  var fp = require('lodash/fp');
@@ -26,6 +27,7 @@ var crypto = require('crypto');
26
27
  var express = require('express');
27
28
  var Router = require('express-promise-router');
28
29
  var yn = require('yn');
30
+ var zod = require('zod');
29
31
  var uuid = require('uuid');
30
32
  var luxon = require('luxon');
31
33
  var promClient = require('prom-client');
@@ -206,7 +208,7 @@ class AnnotateLocationEntityProcessor {
206
208
  base: location.target
207
209
  });
208
210
  if (sourceUrl) {
209
- sourceLocation = catalogModel.stringifyLocationReference({
211
+ sourceLocation = catalogModel.stringifyLocationRef({
210
212
  type: "url",
211
213
  target: sourceUrl
212
214
  });
@@ -215,11 +217,11 @@ class AnnotateLocationEntityProcessor {
215
217
  return lodash.merge({
216
218
  metadata: {
217
219
  annotations: lodash.pickBy({
218
- [catalogModel.LOCATION_ANNOTATION]: catalogModel.stringifyLocationReference(location),
219
- [catalogModel.ORIGIN_LOCATION_ANNOTATION]: catalogModel.stringifyLocationReference(originLocation),
220
- [catalogModel.VIEW_URL_ANNOTATION]: viewUrl,
221
- [catalogModel.EDIT_URL_ANNOTATION]: editUrl,
222
- [catalogModel.SOURCE_LOCATION_ANNOTATION]: sourceLocation
220
+ [catalogModel.ANNOTATION_LOCATION]: catalogModel.stringifyLocationRef(location),
221
+ [catalogModel.ANNOTATION_ORIGIN_LOCATION]: catalogModel.stringifyLocationRef(originLocation),
222
+ [catalogModel.ANNOTATION_VIEW_URL]: viewUrl,
223
+ [catalogModel.ANNOTATION_EDIT_URL]: editUrl,
224
+ [catalogModel.ANNOTATION_SOURCE_LOCATION]: sourceLocation
223
225
  }, lodash.identity)
224
226
  }
225
227
  }, entity);
@@ -650,7 +652,7 @@ class BuiltinKindsEntityProcessor {
650
652
  catalogModel.resourceEntityV1alpha1Validator,
651
653
  catalogModel.groupEntityV1alpha1Validator,
652
654
  catalogModel.locationEntityV1alpha1Validator,
653
- catalogModel.templateEntityV1beta2Validator,
655
+ pluginScaffolderCommon.templateEntityV1beta2Validator,
654
656
  catalogModel.userEntityV1alpha1Validator,
655
657
  catalogModel.systemEntityV1alpha1Validator,
656
658
  catalogModel.domainEntityV1alpha1Validator
@@ -718,6 +720,7 @@ class BuiltinKindsEntityProcessor {
718
720
  const resource = entity;
719
721
  doEmit(resource.spec.owner, { defaultKind: "Group", defaultNamespace: selfRef.namespace }, catalogModel.RELATION_OWNED_BY, catalogModel.RELATION_OWNER_OF);
720
722
  doEmit(resource.spec.dependsOn, { defaultNamespace: selfRef.namespace }, catalogModel.RELATION_DEPENDS_ON, catalogModel.RELATION_DEPENDENCY_OF);
723
+ doEmit(resource.spec.dependencyOf, { defaultNamespace: selfRef.namespace }, catalogModel.RELATION_DEPENDENCY_OF, catalogModel.RELATION_DEPENDS_ON);
721
724
  doEmit(resource.spec.system, { defaultKind: "System", defaultNamespace: selfRef.namespace }, catalogModel.RELATION_PART_OF, catalogModel.RELATION_HAS_PART);
722
725
  }
723
726
  if (entity.kind === "User") {
@@ -1814,14 +1817,14 @@ function* parseEntityYaml(data, location) {
1814
1817
  try {
1815
1818
  documents = yaml__default["default"].parseAllDocuments(data.toString("utf8")).filter((d) => d);
1816
1819
  } catch (e) {
1817
- const loc = catalogModel.stringifyLocationReference(location);
1820
+ const loc = catalogModel.stringifyLocationRef(location);
1818
1821
  const message = `Failed to parse YAML at ${loc}, ${e}`;
1819
1822
  yield generalError(location, message);
1820
1823
  return;
1821
1824
  }
1822
1825
  for (const document of documents) {
1823
1826
  if ((_a = document.errors) == null ? void 0 : _a.length) {
1824
- const loc = catalogModel.stringifyLocationReference(location);
1827
+ const loc = catalogModel.stringifyLocationRef(location);
1825
1828
  const message = `YAML error at ${loc}, ${document.errors[0]}`;
1826
1829
  yield generalError(location, message);
1827
1830
  } else {
@@ -1920,8 +1923,8 @@ function withLocations(baseUrl, org, entity) {
1920
1923
  return lodash.merge({
1921
1924
  metadata: {
1922
1925
  annotations: {
1923
- [catalogModel.LOCATION_ANNOTATION]: location,
1924
- [catalogModel.ORIGIN_LOCATION_ANNOTATION]: location
1926
+ [catalogModel.ANNOTATION_LOCATION]: location,
1927
+ [catalogModel.ANNOTATION_ORIGIN_LOCATION]: location
1925
1928
  }
1926
1929
  }
1927
1930
  }, entity);
@@ -2037,18 +2040,18 @@ function locationSpecToLocationEntity(location, parentEntity) {
2037
2040
  let ownLocation;
2038
2041
  let originLocation;
2039
2042
  if (parentEntity) {
2040
- const maybeOwnLocation = (_a = parentEntity.metadata.annotations) == null ? void 0 : _a[catalogModel.LOCATION_ANNOTATION];
2043
+ const maybeOwnLocation = (_a = parentEntity.metadata.annotations) == null ? void 0 : _a[catalogModel.ANNOTATION_LOCATION];
2041
2044
  if (!maybeOwnLocation) {
2042
- throw new Error(`Parent entity '${catalogModel.stringifyEntityRef(parentEntity)}' of location '${catalogModel.stringifyLocationReference(location)}' does not have a location annotation`);
2045
+ throw new Error(`Parent entity '${catalogModel.stringifyEntityRef(parentEntity)}' of location '${catalogModel.stringifyLocationRef(location)}' does not have a location annotation`);
2043
2046
  }
2044
2047
  ownLocation = maybeOwnLocation;
2045
- const maybeOriginLocation = (_b = parentEntity.metadata.annotations) == null ? void 0 : _b[catalogModel.ORIGIN_LOCATION_ANNOTATION];
2048
+ const maybeOriginLocation = (_b = parentEntity.metadata.annotations) == null ? void 0 : _b[catalogModel.ANNOTATION_ORIGIN_LOCATION];
2046
2049
  if (!maybeOriginLocation) {
2047
- throw new Error(`Parent entity '${catalogModel.stringifyEntityRef(parentEntity)}' of location '${catalogModel.stringifyLocationReference(location)}' does not have an origin location annotation`);
2050
+ throw new Error(`Parent entity '${catalogModel.stringifyEntityRef(parentEntity)}' of location '${catalogModel.stringifyLocationRef(location)}' does not have an origin location annotation`);
2048
2051
  }
2049
2052
  originLocation = maybeOriginLocation;
2050
2053
  } else {
2051
- ownLocation = catalogModel.stringifyLocationReference(location);
2054
+ ownLocation = catalogModel.stringifyLocationRef(location);
2052
2055
  originLocation = ownLocation;
2053
2056
  }
2054
2057
  const result = {
@@ -2057,8 +2060,8 @@ function locationSpecToLocationEntity(location, parentEntity) {
2057
2060
  metadata: {
2058
2061
  name: locationSpecToMetadataName(location),
2059
2062
  annotations: {
2060
- [catalogModel.LOCATION_ANNOTATION]: ownLocation,
2061
- [catalogModel.ORIGIN_LOCATION_ANNOTATION]: originLocation
2063
+ [catalogModel.ANNOTATION_LOCATION]: ownLocation,
2064
+ [catalogModel.ANNOTATION_ORIGIN_LOCATION]: originLocation
2062
2065
  }
2063
2066
  },
2064
2067
  spec: {
@@ -2075,7 +2078,7 @@ function isLocationEntity(entity) {
2075
2078
  }
2076
2079
  function getEntityLocationRef(entity) {
2077
2080
  var _a;
2078
- const ref = (_a = entity.metadata.annotations) == null ? void 0 : _a[catalogModel.LOCATION_ANNOTATION];
2081
+ const ref = (_a = entity.metadata.annotations) == null ? void 0 : _a[catalogModel.ANNOTATION_LOCATION];
2079
2082
  if (!ref) {
2080
2083
  const entityRef = catalogModel.stringifyEntityRef(entity);
2081
2084
  throw new errors.InputError(`Entity '${entityRef}' does not have a location`);
@@ -2084,7 +2087,7 @@ function getEntityLocationRef(entity) {
2084
2087
  }
2085
2088
  function getEntityOriginLocationRef(entity) {
2086
2089
  var _a;
2087
- const ref = (_a = entity.metadata.annotations) == null ? void 0 : _a[catalogModel.ORIGIN_LOCATION_ANNOTATION];
2090
+ const ref = (_a = entity.metadata.annotations) == null ? void 0 : _a[catalogModel.ANNOTATION_ORIGIN_LOCATION];
2088
2091
  if (!ref) {
2089
2092
  const entityRef = catalogModel.stringifyEntityRef(entity);
2090
2093
  throw new errors.InputError(`Entity '${entityRef}' does not have an origin location`);
@@ -2150,7 +2153,7 @@ class ProcessorOutputCollector {
2150
2153
  this.errors.push(e);
2151
2154
  return;
2152
2155
  }
2153
- const location = catalogModel.stringifyLocationReference(i.location);
2156
+ const location = catalogModel.stringifyLocationRef(i.location);
2154
2157
  const annotations = entity.metadata.annotations || {};
2155
2158
  if (typeof annotations === "object" && !Array.isArray(annotations)) {
2156
2159
  const originLocation = getEntityOriginLocationRef(this.parentEntity);
@@ -2160,8 +2163,8 @@ class ProcessorOutputCollector {
2160
2163
  ...entity.metadata,
2161
2164
  annotations: {
2162
2165
  ...annotations,
2163
- [catalogModel.ORIGIN_LOCATION_ANNOTATION]: originLocation,
2164
- [catalogModel.LOCATION_ANNOTATION]: location
2166
+ [catalogModel.ANNOTATION_ORIGIN_LOCATION]: originLocation,
2167
+ [catalogModel.ANNOTATION_LOCATION]: location
2165
2168
  }
2166
2169
  }
2167
2170
  };
@@ -2282,8 +2285,8 @@ class DefaultCatalogProcessingOrchestrator {
2282
2285
  }
2283
2286
  const context = {
2284
2287
  entityRef: catalogModel.stringifyEntityRef(entity),
2285
- location: catalogModel.parseLocationReference(getEntityLocationRef(entity)),
2286
- originLocation: catalogModel.parseLocationReference(getEntityOriginLocationRef(entity)),
2288
+ location: catalogModel.parseLocationRef(getEntityLocationRef(entity)),
2289
+ originLocation: catalogModel.parseLocationRef(getEntityOriginLocationRef(entity)),
2287
2290
  cache,
2288
2291
  collector
2289
2292
  };
@@ -2297,7 +2300,7 @@ class DefaultCatalogProcessingOrchestrator {
2297
2300
  const collectorResults = context.collector.results();
2298
2301
  for (const deferredEntity of collectorResults.deferredEntities) {
2299
2302
  if (!this.options.rulesEnforcer.isAllowed(deferredEntity.entity, context.originLocation)) {
2300
- throw new errors.NotAllowedError(`Entity ${catalogModel.stringifyEntityRef(deferredEntity.entity)} at ${catalogModel.stringifyLocationReference(context.location)}, originated at ${catalogModel.stringifyLocationReference(context.originLocation)}, is not of an allowed kind for that location`);
2303
+ throw new errors.NotAllowedError(`Entity ${catalogModel.stringifyEntityRef(deferredEntity.entity)} at ${catalogModel.stringifyLocationRef(context.location)}, originated at ${catalogModel.stringifyLocationRef(context.originLocation)}, is not of an allowed kind for that location`);
2301
2304
  }
2302
2305
  }
2303
2306
  return {
@@ -2565,14 +2568,18 @@ async function requireRequestBody(req) {
2565
2568
  }
2566
2569
  return body;
2567
2570
  }
2571
+ const locationSpec = zod.z.object({
2572
+ type: zod.z.string(),
2573
+ target: zod.z.string(),
2574
+ presence: zod.z.literal("required").or(zod.z.literal("optional")).optional()
2575
+ }).strict();
2568
2576
  async function validateRequestBody(req, schema) {
2569
2577
  const body = await requireRequestBody(req);
2570
2578
  try {
2571
- await schema.validate(body, { strict: true });
2579
+ return await schema.parse(body);
2572
2580
  } catch (e) {
2573
2581
  throw new errors.InputError(`Malformed request: ${e}`);
2574
2582
  }
2575
- return body;
2576
2583
  }
2577
2584
  function disallowReadonlyMode(readonly) {
2578
2585
  if (readonly) {
@@ -2661,12 +2668,12 @@ async function createRouter(options) {
2661
2668
  }
2662
2669
  if (locationService) {
2663
2670
  router.post("/locations", async (req, res) => {
2664
- const input = await validateRequestBody(req, catalogModel.locationSpecSchema);
2671
+ const location = await validateRequestBody(req, locationSpec);
2665
2672
  const dryRun = yn__default["default"](req.query.dryRun, { default: false });
2666
2673
  if (!dryRun) {
2667
2674
  disallowReadonlyMode(readonlyEnabled);
2668
2675
  }
2669
- const output = await locationService.createLocation(input, dryRun, {
2676
+ const output = await locationService.createLocation(location, dryRun, {
2670
2677
  authorizationToken: getBearerToken(req.header("authorization"))
2671
2678
  });
2672
2679
  res.status(201).json(output);
@@ -2692,8 +2699,9 @@ async function createRouter(options) {
2692
2699
  }
2693
2700
  if (locationAnalyzer) {
2694
2701
  router.post("/analyze-location", async (req, res) => {
2695
- const input = await validateRequestBody(req, catalogModel.analyzeLocationSchema);
2696
- const output = await locationAnalyzer.analyzeLocation(input);
2702
+ const body = await validateRequestBody(req, zod.z.object({ location: locationSpec }));
2703
+ const schema = zod.z.object({ location: locationSpec });
2704
+ const output = await locationAnalyzer.analyzeLocation(schema.parse(body));
2697
2705
  res.status(200).json(output);
2698
2706
  });
2699
2707
  }
@@ -2939,7 +2947,7 @@ class DefaultProcessingDatabase {
2939
2947
  }
2940
2948
  async replaceUnprocessedEntities(txOpaque, options) {
2941
2949
  const tx = txOpaque;
2942
- const { toUpsert, toRemove } = await this.createDelta(tx, options);
2950
+ const { toAdd, toUpsert, toRemove } = await this.createDelta(tx, options);
2943
2951
  if (toRemove.length) {
2944
2952
  const removedCount = await tx("refresh_state").whereIn("entity_ref", function orphanedEntityRefs(orphans) {
2945
2953
  return orphans.withRecursive("descendants", function descendants(outer) {
@@ -2974,6 +2982,33 @@ class DefaultProcessingDatabase {
2974
2982
  await tx("refresh_state_references").where("source_key", "=", options.sourceKey).whereIn("target_entity_ref", toRemove).delete();
2975
2983
  this.options.logger.debug(`removed, ${removedCount} entities: ${JSON.stringify(toRemove)}`);
2976
2984
  }
2985
+ if (toAdd.length) {
2986
+ for (const chunk of lodash__default["default"].chunk(toAdd, 50)) {
2987
+ try {
2988
+ await tx.batchInsert("refresh_state", chunk.map((item) => ({
2989
+ entity_id: uuid.v4(),
2990
+ entity_ref: catalogModel.stringifyEntityRef(item.deferred.entity),
2991
+ unprocessed_entity: JSON.stringify(item.deferred.entity),
2992
+ unprocessed_hash: item.hash,
2993
+ errors: "",
2994
+ location_key: item.deferred.locationKey,
2995
+ next_update_at: tx.fn.now(),
2996
+ last_discovery_at: tx.fn.now()
2997
+ })), BATCH_SIZE$1);
2998
+ await tx.batchInsert("refresh_state_references", chunk.map((item) => ({
2999
+ source_key: options.sourceKey,
3000
+ target_entity_ref: catalogModel.stringifyEntityRef(item.deferred.entity)
3001
+ })), BATCH_SIZE$1);
3002
+ } catch (error) {
3003
+ if (!backendCommon.isDatabaseConflictError(error)) {
3004
+ throw error;
3005
+ } else {
3006
+ this.options.logger.debug(`Fast insert path failed, falling back to slow path, ${error}`);
3007
+ toUpsert.push(...chunk);
3008
+ }
3009
+ }
3010
+ }
3011
+ }
2977
3012
  if (toUpsert.length) {
2978
3013
  for (const {
2979
3014
  deferred: { entity, locationKey },
@@ -3139,6 +3174,7 @@ class DefaultProcessingDatabase {
3139
3174
  async createDelta(tx, options) {
3140
3175
  if (options.type === "delta") {
3141
3176
  return {
3177
+ toAdd: [],
3142
3178
  toUpsert: options.added.map((e) => ({
3143
3179
  deferred: e,
3144
3180
  hash: generateStableHash$1(e.entity)
@@ -3166,21 +3202,22 @@ class DefaultProcessingDatabase {
3166
3202
  }
3167
3203
  ]));
3168
3204
  const newRefsSet = new Set(items.map((item) => item.ref));
3205
+ const toAdd = new Array();
3169
3206
  const toUpsert = new Array();
3170
3207
  const toRemove = oldRefs.map((row) => row.target_entity_ref).filter((ref) => !newRefsSet.has(ref));
3171
3208
  for (const item of items) {
3172
3209
  const oldRef = oldRefsSet.get(item.ref);
3173
3210
  const upsertItem = { deferred: item.deferred, hash: item.hash };
3174
3211
  if (!oldRef) {
3175
- toUpsert.push(upsertItem);
3212
+ toAdd.push(upsertItem);
3176
3213
  } else if (oldRef.locationKey !== item.deferred.locationKey) {
3177
3214
  toRemove.push(item.ref);
3178
- toUpsert.push(upsertItem);
3215
+ toAdd.push(upsertItem);
3179
3216
  } else if (oldRef.oldEntityHash !== item.hash) {
3180
3217
  toUpsert.push(upsertItem);
3181
3218
  }
3182
3219
  }
3183
- return { toUpsert, toRemove };
3220
+ return { toAdd, toUpsert, toRemove };
3184
3221
  }
3185
3222
  async addUnprocessedEntities(txOpaque, options) {
3186
3223
  const tx = txOpaque;
@@ -3516,8 +3553,8 @@ class DefaultLocationService {
3516
3553
  }),
3517
3554
  namespace: "default",
3518
3555
  annotations: {
3519
- [catalogModel.LOCATION_ANNOTATION]: `${spec.type}:${spec.target}`,
3520
- [catalogModel.ORIGIN_LOCATION_ANNOTATION]: `${spec.type}:${spec.target}`
3556
+ [catalogModel.ANNOTATION_LOCATION]: `${spec.type}:${spec.target}`,
3557
+ [catalogModel.ANNOTATION_ORIGIN_LOCATION]: `${spec.type}:${spec.target}`
3521
3558
  }
3522
3559
  },
3523
3560
  spec: {
@@ -3856,7 +3893,7 @@ function buildEntitySearch(entityId, entity) {
3856
3893
  raw.push({ key: "metadata.namespace", value: entity.metadata.namespace });
3857
3894
  raw.push({ key: "metadata.uid", value: entity.metadata.uid });
3858
3895
  if (!entity.metadata.namespace) {
3859
- raw.push({ key: "metadata.namespace", value: catalogModel.ENTITY_DEFAULT_NAMESPACE });
3896
+ raw.push({ key: "metadata.namespace", value: catalogModel.DEFAULT_NAMESPACE });
3860
3897
  }
3861
3898
  for (const relation of (_a = entity.relations) != null ? _a : []) {
3862
3899
  raw.push({