@backstage/plugin-catalog-backend 1.6.0-next.0 → 1.6.0-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,53 @@
1
1
  # @backstage/plugin-catalog-backend
2
2
 
3
+ ## 1.6.0-next.1
4
+
5
+ ### Minor Changes
6
+
7
+ - c395abb5b2: The catalog no longer stops after the first processor `validateEntityKind`
8
+ method returns `true` when validating entity kind shapes. Instead, it continues
9
+ through all registered processors that have this method, and requires that _at
10
+ least one_ of them returned true.
11
+
12
+ The old behavior of stopping early made it harder to extend existing core kinds
13
+ with additional fields, since the `BuiltinKindsEntityProcessor` is always
14
+ present at the top of the processing chain and ensures that your additional
15
+ validation code would never be run.
16
+
17
+ This is technically a breaking change, although it should not affect anybody
18
+ under normal circumstances, except if you had problematic validation code that
19
+ you were unaware that it was not being run. That code may now start to exhibit
20
+ those problems.
21
+
22
+ If you need to disable this new behavior, `CatalogBuilder` as used in your
23
+ `packages/backend/src/plugins/catalog.ts` file now has a
24
+ `useLegacySingleProcessorValidation()` method to go back to the old behavior.
25
+
26
+ ```diff
27
+ const builder = await CatalogBuilder.create(env);
28
+ +builder.useLegacySingleProcessorValidation();
29
+ ```
30
+
31
+ ### Patch Changes
32
+
33
+ - 2a8e3cc0b5: Optimize `Stitcher` process to be more memory efficient
34
+ - 5b3e2afa45: Fixed deprecated use of `substr` into `substring`.
35
+ - Updated dependencies
36
+ - @backstage/backend-common@0.17.0-next.1
37
+ - @backstage/types@1.0.2-next.1
38
+ - @backstage/backend-plugin-api@0.1.5-next.1
39
+ - @backstage/plugin-catalog-node@1.2.2-next.1
40
+ - @backstage/plugin-permission-node@0.7.2-next.1
41
+ - @backstage/config@1.0.5-next.1
42
+ - @backstage/integration@1.4.1-next.1
43
+ - @backstage/catalog-client@1.2.0-next.1
44
+ - @backstage/catalog-model@1.1.4-next.1
45
+ - @backstage/errors@1.1.4-next.1
46
+ - @backstage/plugin-catalog-common@1.0.9-next.1
47
+ - @backstage/plugin-permission-common@0.7.2-next.1
48
+ - @backstage/plugin-scaffolder-common@1.2.3-next.1
49
+ - @backstage/plugin-search-common@1.1.2-next.1
50
+
3
51
  ## 1.6.0-next.0
4
52
 
5
53
  ### Minor Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-backend",
3
- "version": "1.6.0-next.0",
3
+ "version": "1.6.0-next.1",
4
4
  "main": "../dist/index.cjs.js",
5
5
  "types": "../dist/index.alpha.d.ts"
6
6
  }
@@ -177,6 +177,7 @@ export declare class CatalogBuilder {
177
177
  private locationAnalyzer;
178
178
  private readonly permissionRules;
179
179
  private allowedLocationType;
180
+ private legacySingleProcessorValidation;
180
181
  /**
181
182
  * Creates a catalog builder.
182
183
  */
@@ -306,13 +307,18 @@ export declare class CatalogBuilder {
306
307
  * @param permissionRules - Additional permission rules
307
308
  * @alpha
308
309
  */
309
- addPermissionRules(...permissionRules: Array<CatalogPermissionRule | Array<CatalogPermissionRule>>): void;
310
+ addPermissionRules(...permissionRules: Array<CatalogPermissionRule | Array<CatalogPermissionRule>>): this;
310
311
  /**
311
312
  * Sets up the allowed location types from being registered via the location service.
312
313
  *
313
314
  * @param allowedLocationTypes - the allowed location types
314
315
  */
315
316
  setAllowedLocationTypes(allowedLocationTypes: string[]): CatalogBuilder;
317
+ /**
318
+ * Enables the legacy behaviour of canceling validation early whenever only a
319
+ * single processor declares an entity kind to be valid.
320
+ */
321
+ useLegacySingleProcessorValidation(): this;
316
322
  /**
317
323
  * Wires up and returns all of the component parts of the catalog
318
324
  */
@@ -177,6 +177,7 @@ export declare class CatalogBuilder {
177
177
  private locationAnalyzer;
178
178
  private readonly permissionRules;
179
179
  private allowedLocationType;
180
+ private legacySingleProcessorValidation;
180
181
  /**
181
182
  * Creates a catalog builder.
182
183
  */
@@ -305,6 +306,11 @@ export declare class CatalogBuilder {
305
306
  * @param allowedLocationTypes - the allowed location types
306
307
  */
307
308
  setAllowedLocationTypes(allowedLocationTypes: string[]): CatalogBuilder;
309
+ /**
310
+ * Enables the legacy behaviour of canceling validation early whenever only a
311
+ * single processor declares an entity kind to be valid.
312
+ */
313
+ useLegacySingleProcessorValidation(): this;
308
314
  /**
309
315
  * Wires up and returns all of the component parts of the catalog
310
316
  */
package/dist/index.cjs.js CHANGED
@@ -601,7 +601,7 @@ class PlaceholderProcessor {
601
601
  } else if (keys.length !== 1) {
602
602
  return [data, false];
603
603
  }
604
- const resolverKey = keys[0].substr(1);
604
+ const resolverKey = keys[0].substring(1);
605
605
  const resolverValue = data[keys[0]];
606
606
  const resolver = this.options.resolvers[resolverKey];
607
607
  if (!resolver) {
@@ -2930,13 +2930,16 @@ class DefaultCatalogProcessingOrchestrator {
2930
2930
  e
2931
2931
  );
2932
2932
  }
2933
- let foundKind = false;
2933
+ let valid = false;
2934
2934
  for (const processor of this.options.processors) {
2935
2935
  if (processor.validateEntityKind) {
2936
2936
  try {
2937
- foundKind = await processor.validateEntityKind(entity);
2938
- if (foundKind) {
2939
- break;
2937
+ const thisValid = await processor.validateEntityKind(entity);
2938
+ if (thisValid) {
2939
+ valid = true;
2940
+ if (this.options.legacySingleProcessorValidation) {
2941
+ break;
2942
+ }
2940
2943
  }
2941
2944
  } catch (e) {
2942
2945
  throw new errors.InputError(
@@ -2946,7 +2949,7 @@ class DefaultCatalogProcessingOrchestrator {
2946
2949
  }
2947
2950
  }
2948
2951
  }
2949
- if (!foundKind) {
2952
+ if (!valid) {
2950
2953
  throw new errors.InputError(
2951
2954
  `No processor recognized the entity ${context.entityRef} as valid, possibly caused by a foreign kind or apiVersion`
2952
2955
  );
@@ -3159,22 +3162,24 @@ class Stitcher {
3159
3162
  hash: "",
3160
3163
  stitch_ticket: ticket
3161
3164
  }).onConflict("entity_id").merge(["stitch_ticket"]);
3162
- const result = await this.database.with("incoming_references", function incomingReferences(builder) {
3163
- return builder.from("refresh_state_references").where({ target_entity_ref: entityRef }).count({ count: "*" });
3164
- }).select({
3165
- entityId: "refresh_state.entity_id",
3166
- processedEntity: "refresh_state.processed_entity",
3167
- errors: "refresh_state.errors",
3168
- incomingReferenceCount: "incoming_references.count",
3169
- previousHash: "final_entities.hash",
3170
- relationType: "relations.type",
3171
- relationTarget: "relations.target_entity_ref"
3172
- }).from("refresh_state").where({ "refresh_state.entity_ref": entityRef }).crossJoin(this.database.raw("incoming_references")).leftOuterJoin("final_entities", {
3173
- "final_entities.entity_id": "refresh_state.entity_id"
3174
- }).leftOuterJoin("relations", {
3175
- "relations.source_entity_ref": "refresh_state.entity_ref"
3176
- }).orderBy("relationType", "asc").orderBy("relationTarget", "asc");
3177
- if (!result.length) {
3165
+ const [processedResult, relationsResult] = await Promise.all([
3166
+ this.database.with("incoming_references", function incomingReferences(builder) {
3167
+ return builder.from("refresh_state_references").where({ target_entity_ref: entityRef }).count({ count: "*" });
3168
+ }).select({
3169
+ entityId: "refresh_state.entity_id",
3170
+ processedEntity: "refresh_state.processed_entity",
3171
+ errors: "refresh_state.errors",
3172
+ incomingReferenceCount: "incoming_references.count",
3173
+ previousHash: "final_entities.hash"
3174
+ }).from("refresh_state").where({ "refresh_state.entity_ref": entityRef }).crossJoin(this.database.raw("incoming_references")).leftOuterJoin("final_entities", {
3175
+ "final_entities.entity_id": "refresh_state.entity_id"
3176
+ }),
3177
+ this.database.distinct({
3178
+ relationType: "type",
3179
+ relationTarget: "target_entity_ref"
3180
+ }).from("relations").where({ source_entity_ref: entityRef }).orderBy("relationType", "asc").orderBy("relationTarget", "asc")
3181
+ ]);
3182
+ if (!processedResult.length) {
3178
3183
  this.logger.error(
3179
3184
  `Unable to stitch ${entityRef}, item does not exist in refresh state table`
3180
3185
  );
@@ -3186,7 +3191,7 @@ class Stitcher {
3186
3191
  errors,
3187
3192
  incomingReferenceCount,
3188
3193
  previousHash
3189
- } = result[0];
3194
+ } = processedResult[0];
3190
3195
  if (!processedEntity) {
3191
3196
  this.logger.debug(
3192
3197
  `Unable to stitch ${entityRef}, the entity has not yet been processed`
@@ -3214,11 +3219,7 @@ class Stitcher {
3214
3219
  }));
3215
3220
  }
3216
3221
  }
3217
- const uniqueRelationRows = lodash.uniqBy(
3218
- result,
3219
- (r) => `${r.relationType}:${r.relationTarget}`
3220
- );
3221
- entity.relations = uniqueRelationRows.filter((row) => row.relationType).map((row) => ({
3222
+ entity.relations = relationsResult.filter((row) => row.relationType).map((row) => ({
3222
3223
  type: row.relationType,
3223
3224
  targetRef: row.relationTarget
3224
3225
  }));
@@ -3330,8 +3331,8 @@ function parseEntityFilterString(filterString) {
3330
3331
  const filtersByKey = {};
3331
3332
  for (const statement of statements) {
3332
3333
  const equalsIndex = statement.indexOf("=");
3333
- const key = equalsIndex === -1 ? statement : statement.substr(0, equalsIndex).trim();
3334
- const value = equalsIndex === -1 ? void 0 : statement.substr(equalsIndex + 1).trim();
3334
+ const key = equalsIndex === -1 ? statement : statement.substring(0, equalsIndex).trim();
3335
+ const value = equalsIndex === -1 ? void 0 : statement.substring(equalsIndex + 1).trim();
3335
3336
  if (!key) {
3336
3337
  throw new errors.InputError(
3337
3338
  `Invalid filter, '${statement}' is not a valid statement (expected a string on the form a=b or a= or a)`
@@ -4057,6 +4058,7 @@ class CatalogBuilder {
4057
4058
  maxSeconds: 150
4058
4059
  });
4059
4060
  this.locationAnalyzer = void 0;
4061
+ this.legacySingleProcessorValidation = false;
4060
4062
  this.env = env;
4061
4063
  this.entityPolicies = [];
4062
4064
  this.entityPoliciesReplace = false;
@@ -4138,11 +4140,16 @@ class CatalogBuilder {
4138
4140
  }
4139
4141
  addPermissionRules(...permissionRules) {
4140
4142
  this.permissionRules.push(...permissionRules.flat());
4143
+ return this;
4141
4144
  }
4142
4145
  setAllowedLocationTypes(allowedLocationTypes) {
4143
4146
  this.allowedLocationType = allowedLocationTypes;
4144
4147
  return this;
4145
4148
  }
4149
+ useLegacySingleProcessorValidation() {
4150
+ this.legacySingleProcessorValidation = true;
4151
+ return this;
4152
+ }
4146
4153
  async build() {
4147
4154
  var _a, _b;
4148
4155
  const { config, database, logger, permissions } = this.env;
@@ -4167,7 +4174,8 @@ class CatalogBuilder {
4167
4174
  rulesEnforcer,
4168
4175
  logger,
4169
4176
  parser,
4170
- policy
4177
+ policy,
4178
+ legacySingleProcessorValidation: this.legacySingleProcessorValidation
4171
4179
  });
4172
4180
  const stitcher = new Stitcher(dbClient, logger);
4173
4181
  const unauthorizedEntitiesCatalog = new DefaultEntitiesCatalog(