@backstage/plugin-catalog-backend 3.5.1-next.0 → 3.6.0-next.2
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 +40 -0
- package/dist/database/operations/stitcher/performStitching.cjs.js +2 -4
- package/dist/database/operations/stitcher/performStitching.cjs.js.map +1 -1
- package/dist/database/operations/stitcher/syncSearchRows.cjs.js +103 -0
- package/dist/database/operations/stitcher/syncSearchRows.cjs.js.map +1 -0
- package/dist/processing/DefaultCatalogProcessingEngine.cjs.js +1 -2
- package/dist/processing/DefaultCatalogProcessingEngine.cjs.js.map +1 -1
- package/dist/processing/DefaultCatalogProcessingOrchestrator.cjs.js +2 -2
- package/dist/processing/DefaultCatalogProcessingOrchestrator.cjs.js.map +1 -1
- package/dist/processing/ProcessorOutputCollector.cjs.js +5 -3
- package/dist/processing/ProcessorOutputCollector.cjs.js.map +1 -1
- package/dist/processors/FileReaderProcessor.cjs.js +4 -5
- package/dist/processors/FileReaderProcessor.cjs.js.map +1 -1
- package/dist/processors/UrlReaderProcessor.cjs.js +4 -7
- package/dist/processors/UrlReaderProcessor.cjs.js.map +1 -1
- package/dist/providers/DefaultLocationStore.cjs.js +46 -17
- package/dist/providers/DefaultLocationStore.cjs.js.map +1 -1
- package/dist/service/CatalogBuilder.cjs.js +16 -62
- package/dist/service/CatalogBuilder.cjs.js.map +1 -1
- package/dist/service/CatalogPlugin.cjs.js +0 -23
- package/dist/service/CatalogPlugin.cjs.js.map +1 -1
- package/dist/service/createRouter.cjs.js +0 -4
- package/dist/service/createRouter.cjs.js.map +1 -1
- package/dist/service/request/applyEntityFilterToQuery.cjs.js +22 -13
- package/dist/service/request/applyEntityFilterToQuery.cjs.js.map +1 -1
- package/dist/service/request/applyPredicateEntityFilterToQuery.cjs.js +27 -22
- package/dist/service/request/applyPredicateEntityFilterToQuery.cjs.js.map +1 -1
- package/dist/service/request/searchSubquery.cjs.js +10 -0
- package/dist/service/request/searchSubquery.cjs.js.map +1 -0
- package/dist/util/conversion.cjs.js +10 -1
- package/dist/util/conversion.cjs.js.map +1 -1
- package/migrations/20260403000000_add_location_entity_ref.js +151 -0
- package/package.json +20 -21
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# @backstage/plugin-catalog-backend
|
|
2
2
|
|
|
3
|
+
## 3.6.0-next.2
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d16311f: Added a `location_entity_ref` column to the `locations` database table that stores the full entity ref of the corresponding `kind: Location` catalog entity for each registered location row. The value is pre-computed and persisted so that it no longer needs to be recomputed from the location's type and target on every read.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 7e63730: Removed deprecated `PermissionAuthorizer` support and the `createPermissionIntegrationRouter` fallback path from `CatalogBuilder`. The `permissionsRegistry` service is now required, and `permissions` is always a `PermissionsService`.
|
|
12
|
+
- 056e18e: Removed the internal `addPermissions` and `addPermissionRules` methods from `CatalogBuilder`, and removed the `catalogPermissionExtensionPoint` wiring from `CatalogPlugin`. Custom permission rules and permissions should be registered via `coreServices.permissionsRegistry` directly.
|
|
13
|
+
- 482ceed: Migrated from `assertError` to `toError` for error handling.
|
|
14
|
+
- Updated dependencies
|
|
15
|
+
- @backstage/errors@1.3.0-next.0
|
|
16
|
+
- @backstage/plugin-catalog-node@2.2.0-next.2
|
|
17
|
+
- @backstage/integration@2.0.1-next.0
|
|
18
|
+
- @backstage/backend-openapi-utils@0.6.8-next.2
|
|
19
|
+
- @backstage/backend-plugin-api@1.9.0-next.2
|
|
20
|
+
- @backstage/catalog-client@1.14.1-next.0
|
|
21
|
+
- @backstage/catalog-model@1.7.8-next.0
|
|
22
|
+
- @backstage/config@1.3.7-next.0
|
|
23
|
+
- @backstage/filter-predicates@0.1.2-next.0
|
|
24
|
+
- @backstage/plugin-events-node@0.4.21-next.2
|
|
25
|
+
- @backstage/plugin-permission-common@0.9.8-next.0
|
|
26
|
+
- @backstage/plugin-permission-node@0.10.12-next.2
|
|
27
|
+
- @backstage/plugin-catalog-common@1.1.9-next.0
|
|
28
|
+
|
|
29
|
+
## 3.5.1-next.1
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc).
|
|
34
|
+
- 6884814: Improved catalog entity filter query performance by switching from `IN (subquery)` to `EXISTS (correlated subquery)` patterns. This enables PostgreSQL semi-join optimizations and fixes `NOT IN` NULL-semantics pitfalls by using `NOT EXISTS` instead.
|
|
35
|
+
- 9da73bf: Reduced search table write churn during stitching by syncing only changed rows instead of doing a full delete and re-insert. On Postgres this uses a single writable CTE, on MySQL a temporary table merge with deadlock retry, and on SQLite the previous bulk replace.
|
|
36
|
+
- Updated dependencies
|
|
37
|
+
- @backstage/backend-plugin-api@1.9.0-next.1
|
|
38
|
+
- @backstage/backend-openapi-utils@0.6.8-next.1
|
|
39
|
+
- @backstage/plugin-catalog-node@2.1.1-next.1
|
|
40
|
+
- @backstage/plugin-events-node@0.4.21-next.1
|
|
41
|
+
- @backstage/plugin-permission-node@0.10.12-next.1
|
|
42
|
+
|
|
3
43
|
## 3.5.1-next.0
|
|
4
44
|
|
|
5
45
|
### Patch Changes
|
|
@@ -4,6 +4,7 @@ var catalogClient = require('@backstage/catalog-client');
|
|
|
4
4
|
var catalogModel = require('@backstage/catalog-model');
|
|
5
5
|
var buildEntitySearch = require('./buildEntitySearch.cjs.js');
|
|
6
6
|
var markDeferredStitchCompleted = require('./markDeferredStitchCompleted.cjs.js');
|
|
7
|
+
var syncSearchRows = require('./syncSearchRows.cjs.js');
|
|
7
8
|
var util = require('./util.cjs.js');
|
|
8
9
|
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
9
10
|
|
|
@@ -134,10 +135,7 @@ async function performStitching(options) {
|
|
|
134
135
|
logger.debug(`Entity ${entityRef} is already stitched, skipping write.`);
|
|
135
136
|
return "abandoned";
|
|
136
137
|
}
|
|
137
|
-
await
|
|
138
|
-
await trx("search").where({ entity_id: entityId }).delete();
|
|
139
|
-
await trx.batchInsert("search", searchEntries, util.BATCH_SIZE);
|
|
140
|
-
});
|
|
138
|
+
await syncSearchRows.syncSearchRows(knex, entityId, searchEntries);
|
|
141
139
|
return "changed";
|
|
142
140
|
} catch (error) {
|
|
143
141
|
removeFromStitchQueueOnCompletion = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"performStitching.cjs.js","sources":["../../../../src/database/operations/stitcher/performStitching.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { ENTITY_STATUS_CATALOG_PROCESSING_TYPE } from '@backstage/catalog-client';\nimport {\n ANNOTATION_EDIT_URL,\n ANNOTATION_VIEW_URL,\n EntityRelation,\n} from '@backstage/catalog-model';\nimport { AlphaEntity, EntityStatusItem } from '@backstage/catalog-model/alpha';\nimport { SerializedError } from '@backstage/errors';\nimport { Knex } from 'knex';\nimport { StitchingStrategy } from '../../../stitching/types';\nimport {\n DbFinalEntitiesRow,\n DbRefreshStateRow,\n DbSearchRow,\n DbStitchQueueRow,\n} from '../../tables';\nimport { buildEntitySearch } from './buildEntitySearch';\nimport { markDeferredStitchCompleted } from './markDeferredStitchCompleted';\nimport { BATCH_SIZE, generateStableHash } from './util';\nimport {\n LoggerService,\n isDatabaseConflictError,\n} from '@backstage/backend-plugin-api';\n\n// See https://github.com/facebook/react/blob/f0cf832e1d0c8544c36aa8b310960885a11a847c/packages/react-dom-bindings/src/shared/sanitizeURL.js\nconst scriptProtocolPattern =\n // eslint-disable-next-line no-control-regex\n /^[\\u0000-\\u001F ]*j[\\r\\n\\t]*a[\\r\\n\\t]*v[\\r\\n\\t]*a[\\r\\n\\t]*s[\\r\\n\\t]*c[\\r\\n\\t]*r[\\r\\n\\t]*i[\\r\\n\\t]*p[\\r\\n\\t]*t[\\r\\n\\t]*\\:/i;\n\n/**\n * Performs the act of stitching - to take all of the various outputs from the\n * ingestion process, and stitching them together into the final entity JSON\n * shape.\n */\nexport async function performStitching(options: {\n knex: Knex | Knex.Transaction;\n logger: LoggerService;\n strategy: StitchingStrategy;\n entityRef: string;\n stitchTicket?: string;\n}): Promise<'changed' | 'unchanged' | 'abandoned'> {\n const { knex, logger, entityRef } = options;\n const stitchTicket = options.stitchTicket;\n\n // In deferred mode, the entity is removed from the stitch queue on ANY\n // completion, except when an exception is thrown. In the latter case, the\n // entity will be retried at a later time.\n let removeFromStitchQueueOnCompletion = options.strategy.mode === 'deferred';\n\n try {\n const entityResult = await knex<DbRefreshStateRow>('refresh_state')\n .where({ entity_ref: entityRef })\n .limit(1)\n .select('entity_id');\n if (!entityResult.length) {\n // Entity does no exist in refresh state table, no stitching required.\n return 'abandoned';\n }\n\n // Ensure that a final_entities row exists for this entity.\n try {\n await knex<DbFinalEntitiesRow>('final_entities')\n .insert({\n entity_id: entityResult[0].entity_id,\n hash: '',\n entity_ref: entityRef,\n })\n .onConflict('entity_id')\n .ignore();\n } catch (error) {\n // It's possible to hit a race where a refresh_state table delete + insert\n // is done just after we read the entity_id from it. This conflict is safe\n // to ignore because the current stitching operation will be triggered by\n // the old entry, and the new entry will trigger it's own stitching that\n // will update the entity.\n if (isDatabaseConflictError(error)) {\n logger.debug(`Skipping stitching of ${entityRef}, conflict`, error);\n return 'abandoned';\n }\n\n throw error;\n }\n\n // Selecting from refresh_state and final_entities should yield exactly\n // one row (except in abnormal cases where the stitch was invoked for\n // something that didn't exist at all, in which case it's zero rows).\n // The join with the temporary incoming_references still gives one row.\n const [processedResult, relationsResult] = await Promise.all([\n knex\n .with('incoming_references', function incomingReferences(builder) {\n return builder\n .from('refresh_state_references')\n .where({ target_entity_ref: entityRef })\n .count({ count: '*' });\n })\n .select({\n entityId: 'refresh_state.entity_id',\n processedEntity: 'refresh_state.processed_entity',\n errors: 'refresh_state.errors',\n incomingReferenceCount: 'incoming_references.count',\n previousHash: 'final_entities.hash',\n })\n .from('refresh_state')\n .where({ 'refresh_state.entity_ref': entityRef })\n .crossJoin(knex.raw('incoming_references'))\n .leftOuterJoin('final_entities', {\n 'final_entities.entity_id': 'refresh_state.entity_id',\n }),\n knex\n .distinct({\n relationType: 'type',\n relationTarget: 'target_entity_ref',\n })\n .from('relations')\n .where({ source_entity_ref: entityRef })\n .orderBy('relationType', 'asc')\n .orderBy('relationTarget', 'asc'),\n ]);\n\n // If there were no rows returned, it would mean that there was no\n // matching row even in the refresh_state. This can happen for example\n // if we emit a relation to something that hasn't been ingested yet.\n // It's safe to ignore this stitch attempt in that case.\n if (!processedResult.length) {\n logger.debug(\n `Unable to stitch ${entityRef}, item does not exist in refresh state table`,\n );\n return 'abandoned';\n }\n\n const {\n entityId,\n processedEntity,\n errors,\n incomingReferenceCount,\n previousHash,\n } = processedResult[0];\n\n // If there was no processed entity in place, the target hasn't been\n // through the processing steps yet. It's safe to ignore this stitch\n // attempt in that case, since another stitch will be triggered when\n // that processing has finished.\n if (!processedEntity) {\n logger.debug(\n `Unable to stitch ${entityRef}, the entity has not yet been processed`,\n );\n return 'abandoned';\n }\n\n // Grab the processed entity and stitch all of the relevant data into\n // it\n const entity = JSON.parse(processedEntity) as AlphaEntity;\n const isOrphan = Number(incomingReferenceCount) === 0;\n let statusItems: EntityStatusItem[] = [];\n\n if (isOrphan) {\n logger.debug(`${entityRef} is an orphan`);\n entity.metadata.annotations = {\n ...entity.metadata.annotations,\n ['backstage.io/orphan']: 'true',\n };\n }\n if (errors) {\n const parsedErrors = JSON.parse(errors) as SerializedError[];\n if (Array.isArray(parsedErrors) && parsedErrors.length) {\n statusItems = parsedErrors.map(e => ({\n type: ENTITY_STATUS_CATALOG_PROCESSING_TYPE,\n level: 'error',\n message: `${e.name}: ${e.message}`,\n error: e,\n }));\n }\n }\n // We opt to do this check here as we otherwise can't guarantee that it will be run after all processors\n for (const annotation of [ANNOTATION_VIEW_URL, ANNOTATION_EDIT_URL]) {\n const value = entity.metadata.annotations?.[annotation];\n if (typeof value === 'string' && scriptProtocolPattern.test(value)) {\n entity.metadata.annotations![annotation] =\n 'https://backstage.io/annotation-rejected-for-security-reasons';\n }\n }\n\n // TODO: entityRef is lower case and should be uppercase in the final\n // result\n entity.relations = relationsResult\n .filter(row => row.relationType /* exclude null row, if relevant */)\n .map<EntityRelation>(row => ({\n type: row.relationType!,\n targetRef: row.relationTarget!,\n }));\n if (statusItems.length) {\n entity.status = {\n ...entity.status,\n items: [...(entity.status?.items ?? []), ...statusItems],\n };\n }\n\n // If the output entity was actually not changed, just abort\n const hash = generateStableHash(entity);\n if (hash === previousHash) {\n logger.debug(`Skipped stitching of ${entityRef}, no changes`);\n return 'unchanged';\n }\n\n entity.metadata.uid = entityId;\n if (!entity.metadata.etag) {\n // If the original data source did not have its own etag handling,\n // use the hash as a good-quality etag\n entity.metadata.etag = hash;\n }\n\n // This may throw if the entity is invalid, so we call it before\n // the final_entities write, even though we may end up not needing\n // to write the search index.\n const searchEntries = buildEntitySearch(entityId, entity);\n\n let updateQuery = knex<DbFinalEntitiesRow>('final_entities')\n .update({\n final_entity: JSON.stringify(entity),\n hash,\n last_updated_at: knex.fn.now(),\n })\n .where('entity_id', entityId);\n\n // In deferred mode, guard against concurrent stitchers by checking that\n // the stitch_ticket in stitch_queue still matches what we were given.\n if (options.strategy.mode === 'deferred' && stitchTicket) {\n updateQuery = updateQuery.whereExists(\n knex<DbStitchQueueRow>('stitch_queue')\n .where('stitch_queue.entity_ref', entityRef)\n .where('stitch_queue.stitch_ticket', stitchTicket)\n .select(knex.raw('1')),\n );\n }\n\n const amountOfRowsChanged = await updateQuery;\n\n if (amountOfRowsChanged === 0) {\n logger.debug(`Entity ${entityRef} is already stitched, skipping write.`);\n return 'abandoned';\n }\n\n await knex.transaction(async trx => {\n await trx<DbSearchRow>('search').where({ entity_id: entityId }).delete();\n await trx.batchInsert('search', searchEntries, BATCH_SIZE);\n });\n\n return 'changed';\n } catch (error) {\n removeFromStitchQueueOnCompletion = false;\n throw error;\n } finally {\n if (removeFromStitchQueueOnCompletion && stitchTicket) {\n await markDeferredStitchCompleted({\n knex: knex,\n entityRef,\n stitchTicket,\n });\n }\n }\n}\n"],"names":["isDatabaseConflictError","ENTITY_STATUS_CATALOG_PROCESSING_TYPE","ANNOTATION_VIEW_URL","ANNOTATION_EDIT_URL","generateStableHash","buildEntitySearch","BATCH_SIZE","markDeferredStitchCompleted"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"performStitching.cjs.js","sources":["../../../../src/database/operations/stitcher/performStitching.ts"],"sourcesContent":["/*\n * Copyright 2023 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 { ENTITY_STATUS_CATALOG_PROCESSING_TYPE } from '@backstage/catalog-client';\nimport {\n ANNOTATION_EDIT_URL,\n ANNOTATION_VIEW_URL,\n EntityRelation,\n} from '@backstage/catalog-model';\nimport { AlphaEntity, EntityStatusItem } from '@backstage/catalog-model/alpha';\nimport { SerializedError } from '@backstage/errors';\nimport { Knex } from 'knex';\nimport { StitchingStrategy } from '../../../stitching/types';\nimport {\n DbFinalEntitiesRow,\n DbRefreshStateRow,\n DbStitchQueueRow,\n} from '../../tables';\nimport { buildEntitySearch } from './buildEntitySearch';\nimport { markDeferredStitchCompleted } from './markDeferredStitchCompleted';\nimport { syncSearchRows } from './syncSearchRows';\nimport { generateStableHash } from './util';\nimport {\n LoggerService,\n isDatabaseConflictError,\n} from '@backstage/backend-plugin-api';\n\n// See https://github.com/facebook/react/blob/f0cf832e1d0c8544c36aa8b310960885a11a847c/packages/react-dom-bindings/src/shared/sanitizeURL.js\nconst scriptProtocolPattern =\n // eslint-disable-next-line no-control-regex\n /^[\\u0000-\\u001F ]*j[\\r\\n\\t]*a[\\r\\n\\t]*v[\\r\\n\\t]*a[\\r\\n\\t]*s[\\r\\n\\t]*c[\\r\\n\\t]*r[\\r\\n\\t]*i[\\r\\n\\t]*p[\\r\\n\\t]*t[\\r\\n\\t]*\\:/i;\n\n/**\n * Performs the act of stitching - to take all of the various outputs from the\n * ingestion process, and stitching them together into the final entity JSON\n * shape.\n */\nexport async function performStitching(options: {\n knex: Knex | Knex.Transaction;\n logger: LoggerService;\n strategy: StitchingStrategy;\n entityRef: string;\n stitchTicket?: string;\n}): Promise<'changed' | 'unchanged' | 'abandoned'> {\n const { knex, logger, entityRef } = options;\n const stitchTicket = options.stitchTicket;\n\n // In deferred mode, the entity is removed from the stitch queue on ANY\n // completion, except when an exception is thrown. In the latter case, the\n // entity will be retried at a later time.\n let removeFromStitchQueueOnCompletion = options.strategy.mode === 'deferred';\n\n try {\n const entityResult = await knex<DbRefreshStateRow>('refresh_state')\n .where({ entity_ref: entityRef })\n .limit(1)\n .select('entity_id');\n if (!entityResult.length) {\n // Entity does no exist in refresh state table, no stitching required.\n return 'abandoned';\n }\n\n // Ensure that a final_entities row exists for this entity.\n try {\n await knex<DbFinalEntitiesRow>('final_entities')\n .insert({\n entity_id: entityResult[0].entity_id,\n hash: '',\n entity_ref: entityRef,\n })\n .onConflict('entity_id')\n .ignore();\n } catch (error) {\n // It's possible to hit a race where a refresh_state table delete + insert\n // is done just after we read the entity_id from it. This conflict is safe\n // to ignore because the current stitching operation will be triggered by\n // the old entry, and the new entry will trigger it's own stitching that\n // will update the entity.\n if (isDatabaseConflictError(error)) {\n logger.debug(`Skipping stitching of ${entityRef}, conflict`, error);\n return 'abandoned';\n }\n\n throw error;\n }\n\n // Selecting from refresh_state and final_entities should yield exactly\n // one row (except in abnormal cases where the stitch was invoked for\n // something that didn't exist at all, in which case it's zero rows).\n // The join with the temporary incoming_references still gives one row.\n const [processedResult, relationsResult] = await Promise.all([\n knex\n .with('incoming_references', function incomingReferences(builder) {\n return builder\n .from('refresh_state_references')\n .where({ target_entity_ref: entityRef })\n .count({ count: '*' });\n })\n .select({\n entityId: 'refresh_state.entity_id',\n processedEntity: 'refresh_state.processed_entity',\n errors: 'refresh_state.errors',\n incomingReferenceCount: 'incoming_references.count',\n previousHash: 'final_entities.hash',\n })\n .from('refresh_state')\n .where({ 'refresh_state.entity_ref': entityRef })\n .crossJoin(knex.raw('incoming_references'))\n .leftOuterJoin('final_entities', {\n 'final_entities.entity_id': 'refresh_state.entity_id',\n }),\n knex\n .distinct({\n relationType: 'type',\n relationTarget: 'target_entity_ref',\n })\n .from('relations')\n .where({ source_entity_ref: entityRef })\n .orderBy('relationType', 'asc')\n .orderBy('relationTarget', 'asc'),\n ]);\n\n // If there were no rows returned, it would mean that there was no\n // matching row even in the refresh_state. This can happen for example\n // if we emit a relation to something that hasn't been ingested yet.\n // It's safe to ignore this stitch attempt in that case.\n if (!processedResult.length) {\n logger.debug(\n `Unable to stitch ${entityRef}, item does not exist in refresh state table`,\n );\n return 'abandoned';\n }\n\n const {\n entityId,\n processedEntity,\n errors,\n incomingReferenceCount,\n previousHash,\n } = processedResult[0];\n\n // If there was no processed entity in place, the target hasn't been\n // through the processing steps yet. It's safe to ignore this stitch\n // attempt in that case, since another stitch will be triggered when\n // that processing has finished.\n if (!processedEntity) {\n logger.debug(\n `Unable to stitch ${entityRef}, the entity has not yet been processed`,\n );\n return 'abandoned';\n }\n\n // Grab the processed entity and stitch all of the relevant data into\n // it\n const entity = JSON.parse(processedEntity) as AlphaEntity;\n const isOrphan = Number(incomingReferenceCount) === 0;\n let statusItems: EntityStatusItem[] = [];\n\n if (isOrphan) {\n logger.debug(`${entityRef} is an orphan`);\n entity.metadata.annotations = {\n ...entity.metadata.annotations,\n ['backstage.io/orphan']: 'true',\n };\n }\n if (errors) {\n const parsedErrors = JSON.parse(errors) as SerializedError[];\n if (Array.isArray(parsedErrors) && parsedErrors.length) {\n statusItems = parsedErrors.map(e => ({\n type: ENTITY_STATUS_CATALOG_PROCESSING_TYPE,\n level: 'error',\n message: `${e.name}: ${e.message}`,\n error: e,\n }));\n }\n }\n // We opt to do this check here as we otherwise can't guarantee that it will be run after all processors\n for (const annotation of [ANNOTATION_VIEW_URL, ANNOTATION_EDIT_URL]) {\n const value = entity.metadata.annotations?.[annotation];\n if (typeof value === 'string' && scriptProtocolPattern.test(value)) {\n entity.metadata.annotations![annotation] =\n 'https://backstage.io/annotation-rejected-for-security-reasons';\n }\n }\n\n // TODO: entityRef is lower case and should be uppercase in the final\n // result\n entity.relations = relationsResult\n .filter(row => row.relationType /* exclude null row, if relevant */)\n .map<EntityRelation>(row => ({\n type: row.relationType!,\n targetRef: row.relationTarget!,\n }));\n if (statusItems.length) {\n entity.status = {\n ...entity.status,\n items: [...(entity.status?.items ?? []), ...statusItems],\n };\n }\n\n // If the output entity was actually not changed, just abort\n const hash = generateStableHash(entity);\n if (hash === previousHash) {\n logger.debug(`Skipped stitching of ${entityRef}, no changes`);\n return 'unchanged';\n }\n\n entity.metadata.uid = entityId;\n if (!entity.metadata.etag) {\n // If the original data source did not have its own etag handling,\n // use the hash as a good-quality etag\n entity.metadata.etag = hash;\n }\n\n // This may throw if the entity is invalid, so we call it before\n // the final_entities write, even though we may end up not needing\n // to write the search index.\n const searchEntries = buildEntitySearch(entityId, entity);\n\n let updateQuery = knex<DbFinalEntitiesRow>('final_entities')\n .update({\n final_entity: JSON.stringify(entity),\n hash,\n last_updated_at: knex.fn.now(),\n })\n .where('entity_id', entityId);\n\n // In deferred mode, guard against concurrent stitchers by checking that\n // the stitch_ticket in stitch_queue still matches what we were given.\n if (options.strategy.mode === 'deferred' && stitchTicket) {\n updateQuery = updateQuery.whereExists(\n knex<DbStitchQueueRow>('stitch_queue')\n .where('stitch_queue.entity_ref', entityRef)\n .where('stitch_queue.stitch_ticket', stitchTicket)\n .select(knex.raw('1')),\n );\n }\n\n const amountOfRowsChanged = await updateQuery;\n\n if (amountOfRowsChanged === 0) {\n logger.debug(`Entity ${entityRef} is already stitched, skipping write.`);\n return 'abandoned';\n }\n\n await syncSearchRows(knex, entityId, searchEntries);\n\n return 'changed';\n } catch (error) {\n removeFromStitchQueueOnCompletion = false;\n throw error;\n } finally {\n if (removeFromStitchQueueOnCompletion && stitchTicket) {\n await markDeferredStitchCompleted({\n knex: knex,\n entityRef,\n stitchTicket,\n });\n }\n }\n}\n"],"names":["isDatabaseConflictError","ENTITY_STATUS_CATALOG_PROCESSING_TYPE","ANNOTATION_VIEW_URL","ANNOTATION_EDIT_URL","generateStableHash","buildEntitySearch","syncSearchRows","markDeferredStitchCompleted"],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var util = require('./util.cjs.js');
|
|
4
|
+
|
|
5
|
+
const NULL_SENTINEL = "";
|
|
6
|
+
function filterSentinelValues(entries) {
|
|
7
|
+
return entries.filter(
|
|
8
|
+
(r) => r.value !== NULL_SENTINEL && r.original_value !== NULL_SENTINEL
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
async function syncSearchRows(knex, entityId, searchEntries) {
|
|
12
|
+
const client = knex.client.config.client;
|
|
13
|
+
if (client === "pg") {
|
|
14
|
+
await syncPostgres(knex, entityId, searchEntries);
|
|
15
|
+
} else if (client.includes("mysql")) {
|
|
16
|
+
await syncMysql(knex, entityId, searchEntries);
|
|
17
|
+
} else {
|
|
18
|
+
await syncBulkReplace(knex, entityId, searchEntries);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function syncPostgres(knex, entityId, searchEntries) {
|
|
22
|
+
const filtered = filterSentinelValues(searchEntries);
|
|
23
|
+
const keys = filtered.map((r) => r.key);
|
|
24
|
+
const values = filtered.map((r) => r.value);
|
|
25
|
+
const originalValues = filtered.map((r) => r.original_value);
|
|
26
|
+
await knex.raw(
|
|
27
|
+
`
|
|
28
|
+
WITH desired AS (
|
|
29
|
+
SELECT *
|
|
30
|
+
FROM unnest(?::text[], ?::text[], ?::text[])
|
|
31
|
+
AS d(key, value, original_value)
|
|
32
|
+
),
|
|
33
|
+
deleted AS (
|
|
34
|
+
DELETE FROM "search" s
|
|
35
|
+
WHERE s.entity_id = ?
|
|
36
|
+
AND NOT EXISTS (
|
|
37
|
+
SELECT 1 FROM desired d
|
|
38
|
+
WHERE d.key = s.key
|
|
39
|
+
AND COALESCE(d.value, chr(1)) = COALESCE(s.value, chr(1))
|
|
40
|
+
AND COALESCE(d.original_value, chr(1)) = COALESCE(s.original_value, chr(1))
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
INSERT INTO "search" (entity_id, key, value, original_value)
|
|
44
|
+
SELECT ?, d.key, d.value, d.original_value
|
|
45
|
+
FROM desired d
|
|
46
|
+
WHERE NOT EXISTS (
|
|
47
|
+
SELECT 1 FROM "search" s
|
|
48
|
+
WHERE s.entity_id = ?
|
|
49
|
+
AND s.key = d.key
|
|
50
|
+
AND COALESCE(s.value, chr(1)) = COALESCE(d.value, chr(1))
|
|
51
|
+
AND COALESCE(s.original_value, chr(1)) = COALESCE(d.original_value, chr(1))
|
|
52
|
+
)
|
|
53
|
+
`,
|
|
54
|
+
[keys, values, originalValues, entityId, entityId, entityId]
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const MYSQL_DEADLOCK_MAX_RETRIES = 3;
|
|
58
|
+
async function syncMysql(knex, entityId, searchEntries) {
|
|
59
|
+
for (let attempt = 1; ; attempt++) {
|
|
60
|
+
try {
|
|
61
|
+
await knex.transaction(async (trx) => {
|
|
62
|
+
await trx.raw(
|
|
63
|
+
"CREATE TEMPORARY TABLE IF NOT EXISTS `_desired_search` (`key` VARCHAR(255) NOT NULL, `value` VARCHAR(255) NULL, `original_value` VARCHAR(255) NULL)"
|
|
64
|
+
);
|
|
65
|
+
await trx.raw("DELETE FROM `_desired_search`");
|
|
66
|
+
if (searchEntries.length > 0) {
|
|
67
|
+
await trx.batchInsert(
|
|
68
|
+
"_desired_search",
|
|
69
|
+
searchEntries.map((r) => ({
|
|
70
|
+
key: r.key,
|
|
71
|
+
value: r.value,
|
|
72
|
+
original_value: r.original_value
|
|
73
|
+
})),
|
|
74
|
+
util.BATCH_SIZE
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
await trx.raw(
|
|
78
|
+
"DELETE s FROM `search` s WHERE s.entity_id = ? AND NOT EXISTS ( SELECT 1 FROM `_desired_search` d WHERE d.`key` = s.`key` AND d.`value` <=> s.`value` AND BINARY d.`original_value` <=> BINARY s.`original_value`)",
|
|
79
|
+
[entityId]
|
|
80
|
+
);
|
|
81
|
+
await trx.raw(
|
|
82
|
+
"INSERT INTO `search` (entity_id, `key`, `value`, `original_value`) SELECT ?, d.`key`, d.`value`, d.`original_value` FROM `_desired_search` d WHERE NOT EXISTS ( SELECT 1 FROM `search` s WHERE s.entity_id = ? AND s.`key` = d.`key` AND s.`value` <=> d.`value` AND BINARY s.`original_value` <=> BINARY d.`original_value`)",
|
|
83
|
+
[entityId, entityId]
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (error?.errno === 1213 && attempt < MYSQL_DEADLOCK_MAX_RETRIES) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function syncBulkReplace(knex, entityId, searchEntries) {
|
|
96
|
+
await knex.transaction(async (trx) => {
|
|
97
|
+
await trx("search").where({ entity_id: entityId }).delete();
|
|
98
|
+
await trx.batchInsert("search", searchEntries, util.BATCH_SIZE);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
exports.syncSearchRows = syncSearchRows;
|
|
103
|
+
//# sourceMappingURL=syncSearchRows.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncSearchRows.cjs.js","sources":["../../../../src/database/operations/stitcher/syncSearchRows.ts"],"sourcesContent":["/*\n * Copyright 2026 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 { Knex } from 'knex';\nimport { DbSearchRow } from '../../tables';\nimport { BATCH_SIZE } from './util';\n\n// The Postgres sync uses COALESCE(x, NULL_SENTINEL) to allow Postgres to\n// include nullable columns in the Hash Cond of anti-joins (IS NOT DISTINCT\n// FROM prevents this). As a consequence, values that are exactly this\n// sentinel character are not searchable — they would be treated as NULL.\n// This is the SOH (Start of Heading) control character which does not\n// appear in real entity metadata.\nconst NULL_SENTINEL = '\\x01';\n\nfunction filterSentinelValues(entries: DbSearchRow[]): DbSearchRow[] {\n return entries.filter(\n r => r.value !== NULL_SENTINEL && r.original_value !== NULL_SENTINEL,\n );\n}\n\n/**\n * Synchronizes the search table rows for a given entity, applying only the\n * minimal set of changes needed. Rows that already exist with the correct\n * values are left untouched, new rows are inserted, and stale rows are\n * deleted — minimizing write churn, dead tuples, and WAL traffic.\n *\n * Uses database-specific strategies:\n * - Postgres: Single writable CTE with unnest (one round-trip, no DDL)\n * - MySQL: Temporary table merge (two queries in a transaction)\n * - SQLite: Simple bulk replace (sufficient for dev/test)\n */\nexport async function syncSearchRows(\n knex: Knex | Knex.Transaction,\n entityId: string,\n searchEntries: DbSearchRow[],\n): Promise<void> {\n const client = knex.client.config.client;\n\n if (client === 'pg') {\n await syncPostgres(knex, entityId, searchEntries);\n } else if (client.includes('mysql')) {\n await syncMysql(knex, entityId, searchEntries);\n } else {\n await syncBulkReplace(knex, entityId, searchEntries);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Postgres: writable CTE + unnest\n//\n// All CTE branches see the same pre-modification snapshot, so the DELETE\n// and INSERT do not interfere with each other. This is a single atomic\n// statement — no explicit transaction wrapper needed.\n//\n// Nullable columns use COALESCE(x, chr(1)) instead of IS NOT DISTINCT FROM\n// so that Postgres can include all three columns in the Hash Cond of the\n// anti-join, rather than pushing nullable comparisons into a Join Filter\n// that degrades to O(n*m) when many rows share the same key. chr(1) (SOH\n// control character) is used as the NULL sentinel — it cannot appear in\n// real entity values since they are human-readable strings.\n// ---------------------------------------------------------------------------\nasync function syncPostgres(\n knex: Knex | Knex.Transaction,\n entityId: string,\n searchEntries: DbSearchRow[],\n): Promise<void> {\n const filtered = filterSentinelValues(searchEntries);\n const keys = filtered.map(r => r.key);\n const values = filtered.map(r => r.value);\n const originalValues = filtered.map(r => r.original_value);\n\n await knex.raw(\n `\n WITH desired AS (\n SELECT *\n FROM unnest(?::text[], ?::text[], ?::text[])\n AS d(key, value, original_value)\n ),\n deleted AS (\n DELETE FROM \"search\" s\n WHERE s.entity_id = ?\n AND NOT EXISTS (\n SELECT 1 FROM desired d\n WHERE d.key = s.key\n AND COALESCE(d.value, chr(1)) = COALESCE(s.value, chr(1))\n AND COALESCE(d.original_value, chr(1)) = COALESCE(s.original_value, chr(1))\n )\n )\n INSERT INTO \"search\" (entity_id, key, value, original_value)\n SELECT ?, d.key, d.value, d.original_value\n FROM desired d\n WHERE NOT EXISTS (\n SELECT 1 FROM \"search\" s\n WHERE s.entity_id = ?\n AND s.key = d.key\n AND COALESCE(s.value, chr(1)) = COALESCE(d.value, chr(1))\n AND COALESCE(s.original_value, chr(1)) = COALESCE(d.original_value, chr(1))\n )\n `,\n [keys, values, originalValues, entityId, entityId, entityId],\n );\n}\n\n// ---------------------------------------------------------------------------\n// MySQL: temporary table merge with deadlock retry\n//\n// MySQL does not support data-modifying CTEs, so we materialize the desired\n// state into a session-scoped temporary table and then merge it into the\n// real table with two queries. The temp table is created inside the\n// transaction to guarantee it exists on the same pooled connection.\n// CREATE/DROP TEMPORARY TABLE does not cause an implicit commit in MySQL\n// (unlike regular DDL), so this is transaction-safe.\n//\n// InnoDB's next-key (gap) locking can cause deadlocks between concurrent\n// transactions operating on different entity_ids when their gap locks\n// overlap on shared index pages. We retry on deadlock (error 1213) since\n// the operation is idempotent.\n// ---------------------------------------------------------------------------\nconst MYSQL_DEADLOCK_MAX_RETRIES = 3;\n\nasync function syncMysql(\n knex: Knex | Knex.Transaction,\n entityId: string,\n searchEntries: DbSearchRow[],\n): Promise<void> {\n for (let attempt = 1; ; attempt++) {\n try {\n await knex.transaction(async trx => {\n // Create the temp table inside the transaction so it's guaranteed\n // to be on the same pooled connection as the merge queries.\n // CREATE TEMPORARY TABLE does not cause an implicit commit in\n // MySQL (unlike regular CREATE TABLE), so this is safe.\n await trx.raw(\n 'CREATE TEMPORARY TABLE IF NOT EXISTS `_desired_search` (' +\n '`key` VARCHAR(255) NOT NULL, ' +\n '`value` VARCHAR(255) NULL, ' +\n '`original_value` VARCHAR(255) NULL' +\n ')',\n );\n // Clear stale data from any previous call on this connection.\n // Uses DELETE (DML) instead of TRUNCATE (DDL) to avoid an\n // implicit commit that would break transaction atomicity.\n await trx.raw('DELETE FROM `_desired_search`');\n\n if (searchEntries.length > 0) {\n await trx.batchInsert(\n '_desired_search',\n searchEntries.map(r => ({\n key: r.key,\n value: r.value,\n original_value: r.original_value,\n })),\n BATCH_SIZE,\n );\n }\n\n // Delete rows that are no longer in the desired set\n await trx.raw(\n 'DELETE s FROM `search` s ' +\n 'WHERE s.entity_id = ? ' +\n 'AND NOT EXISTS (' +\n ' SELECT 1 FROM `_desired_search` d' +\n ' WHERE d.`key` = s.`key`' +\n ' AND d.`value` <=> s.`value`' +\n ' AND BINARY d.`original_value` <=> BINARY s.`original_value`' +\n ')',\n [entityId],\n );\n\n // Insert rows that are new in the desired set. The original_value\n // column preserves the original casing and must be compared with\n // BINARY to avoid MySQL's default case-insensitive collation\n // treating e.g. \"Team-A\" and \"team-a\" as equal.\n await trx.raw(\n 'INSERT INTO `search` (entity_id, `key`, `value`, `original_value`) ' +\n 'SELECT ?, d.`key`, d.`value`, d.`original_value` ' +\n 'FROM `_desired_search` d ' +\n 'WHERE NOT EXISTS (' +\n ' SELECT 1 FROM `search` s' +\n ' WHERE s.entity_id = ?' +\n ' AND s.`key` = d.`key`' +\n ' AND s.`value` <=> d.`value`' +\n ' AND BINARY s.`original_value` <=> BINARY d.`original_value`' +\n ')',\n [entityId, entityId],\n );\n });\n return;\n } catch (error) {\n // MySQL error 1213: ER_LOCK_DEADLOCK\n if (\n (error as any)?.errno === 1213 &&\n attempt < MYSQL_DEADLOCK_MAX_RETRIES\n ) {\n continue;\n }\n throw error;\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// SQLite (and fallback): bulk replace\n// ---------------------------------------------------------------------------\nasync function syncBulkReplace(\n knex: Knex | Knex.Transaction,\n entityId: string,\n searchEntries: DbSearchRow[],\n): Promise<void> {\n await knex.transaction(async trx => {\n await trx<DbSearchRow>('search').where({ entity_id: entityId }).delete();\n await trx.batchInsert('search', searchEntries, BATCH_SIZE);\n });\n}\n"],"names":["BATCH_SIZE"],"mappings":";;;;AA0BA,MAAM,aAAA,GAAgB,GAAA;AAEtB,SAAS,qBAAqB,OAAA,EAAuC;AACnE,EAAA,OAAO,OAAA,CAAQ,MAAA;AAAA,IACb,CAAA,CAAA,KAAK,CAAA,CAAE,KAAA,KAAU,aAAA,IAAiB,EAAE,cAAA,KAAmB;AAAA,GACzD;AACF;AAaA,eAAsB,cAAA,CACpB,IAAA,EACA,QAAA,EACA,aAAA,EACe;AACf,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,CAAO,MAAA,CAAO,MAAA;AAElC,EAAA,IAAI,WAAW,IAAA,EAAM;AACnB,IAAA,MAAM,YAAA,CAAa,IAAA,EAAM,QAAA,EAAU,aAAa,CAAA;AAAA,EAClD,CAAA,MAAA,IAAW,MAAA,CAAO,QAAA,CAAS,OAAO,CAAA,EAAG;AACnC,IAAA,MAAM,SAAA,CAAU,IAAA,EAAM,QAAA,EAAU,aAAa,CAAA;AAAA,EAC/C,CAAA,MAAO;AACL,IAAA,MAAM,eAAA,CAAgB,IAAA,EAAM,QAAA,EAAU,aAAa,CAAA;AAAA,EACrD;AACF;AAgBA,eAAe,YAAA,CACb,IAAA,EACA,QAAA,EACA,aAAA,EACe;AACf,EAAA,MAAM,QAAA,GAAW,qBAAqB,aAAa,CAAA;AACnD,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,GAAG,CAAA;AACpC,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,KAAK,CAAA;AACxC,EAAA,MAAM,cAAA,GAAiB,QAAA,CAAS,GAAA,CAAI,CAAA,CAAA,KAAK,EAAE,cAAc,CAAA;AAEzD,EAAA,MAAM,IAAA,CAAK,GAAA;AAAA,IACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAA,CAAA;AAAA,IA2BA,CAAC,IAAA,EAAM,MAAA,EAAQ,cAAA,EAAgB,QAAA,EAAU,UAAU,QAAQ;AAAA,GAC7D;AACF;AAiBA,MAAM,0BAAA,GAA6B,CAAA;AAEnC,eAAe,SAAA,CACb,IAAA,EACA,QAAA,EACA,aAAA,EACe;AACf,EAAA,KAAA,IAAS,OAAA,GAAU,KAAK,OAAA,EAAA,EAAW;AACjC,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,CAAK,WAAA,CAAY,OAAM,GAAA,KAAO;AAKlC,QAAA,MAAM,GAAA,CAAI,GAAA;AAAA,UACR;AAAA,SAKF;AAIA,QAAA,MAAM,GAAA,CAAI,IAAI,+BAA+B,CAAA;AAE7C,QAAA,IAAI,aAAA,CAAc,SAAS,CAAA,EAAG;AAC5B,UAAA,MAAM,GAAA,CAAI,WAAA;AAAA,YACR,iBAAA;AAAA,YACA,aAAA,CAAc,IAAI,CAAA,CAAA,MAAM;AAAA,cACtB,KAAK,CAAA,CAAE,GAAA;AAAA,cACP,OAAO,CAAA,CAAE,KAAA;AAAA,cACT,gBAAgB,CAAA,CAAE;AAAA,aACpB,CAAE,CAAA;AAAA,YACFA;AAAA,WACF;AAAA,QACF;AAGA,QAAA,MAAM,GAAA,CAAI,GAAA;AAAA,UACR,4NAAA;AAAA,UAQA,CAAC,QAAQ;AAAA,SACX;AAMA,QAAA,MAAM,GAAA,CAAI,GAAA;AAAA,UACR,0UAAA;AAAA,UAUA,CAAC,UAAU,QAAQ;AAAA,SACrB;AAAA,MACF,CAAC,CAAA;AACD,MAAA;AAAA,IACF,SAAS,KAAA,EAAO;AAEd,MAAA,IACG,KAAA,EAAe,KAAA,KAAU,IAAA,IAC1B,OAAA,GAAU,0BAAA,EACV;AACA,QAAA;AAAA,MACF;AACA,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AACF;AAKA,eAAe,eAAA,CACb,IAAA,EACA,QAAA,EACA,aAAA,EACe;AACf,EAAA,MAAM,IAAA,CAAK,WAAA,CAAY,OAAM,GAAA,KAAO;AAClC,IAAA,MAAM,GAAA,CAAiB,QAAQ,CAAA,CAAE,KAAA,CAAM,EAAE,SAAA,EAAW,QAAA,EAAU,CAAA,CAAE,MAAA,EAAO;AACvE,IAAA,MAAM,GAAA,CAAI,WAAA,CAAY,QAAA,EAAU,aAAA,EAAeA,eAAU,CAAA;AAAA,EAC3D,CAAC,CAAA;AACH;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultCatalogProcessingEngine.cjs.js","sources":["../../src/processing/DefaultCatalogProcessingEngine.ts"],"sourcesContent":["/*\n * Copyright 2021 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 {\n ANNOTATION_LOCATION,\n Entity,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { assertError, serializeError, stringifyError } from '@backstage/errors';\nimport { Hash } from 'node:crypto';\nimport stableStringify from 'fast-json-stable-stringify';\nimport { Knex } from 'knex';\nimport { trace } from '@opentelemetry/api';\nimport { ProcessingDatabase, RefreshStateItem } from '../database/types';\nimport { createCounterMetric, createSummaryMetric } from '../util/metrics';\nimport { CatalogProcessingOrchestrator, EntityProcessingResult } from './types';\nimport { Stitcher, stitchingStrategyFromConfig } from '../stitching/types';\nimport { startTaskPipeline } from './TaskPipeline';\nimport { Config } from '@backstage/config';\nimport {\n addEntityAttributes,\n TRACER_ID,\n withActiveSpan,\n} from '../util/opentelemetry';\nimport { deleteOrphanedEntities } from '../database/operations/util/deleteOrphanedEntities';\nimport { EventsService } from '@backstage/plugin-events-node';\nimport { CATALOG_ERRORS_TOPIC } from '../constants';\nimport { LoggerService, SchedulerService } from '@backstage/backend-plugin-api';\nimport { MetricsService } from '@backstage/backend-plugin-api/alpha';\n\nconst CACHE_TTL = 5;\n\nconst tracer = trace.getTracer(TRACER_ID);\n\nexport type ProgressTracker = ReturnType<typeof progressTracker>;\n\nconst stableStringifyArray = (arr: any[]) => {\n const sorted = arr.map(stableStringify).sort();\n return `[${sorted.join(',')}]`;\n};\n\n// NOTE(freben): Perhaps surprisingly, this class does not implement the\n// CatalogProcessingEngine type. That type is externally visible and its name is\n// the way it is for historic reasons. This class has no particular reason to\n// implement that precise interface; nowadays there are several different\n// engines \"hiding\" behind the CatalogProcessingEngine interface, of which this\n// is just one.\nexport class DefaultCatalogProcessingEngine {\n private readonly config: Config;\n private readonly scheduler: SchedulerService;\n private readonly logger: LoggerService;\n private readonly knex: Knex;\n private readonly processingDatabase: ProcessingDatabase;\n private readonly orchestrator: CatalogProcessingOrchestrator;\n private readonly stitcher: Stitcher;\n private readonly createHash: () => Hash;\n private readonly pollingIntervalMs: number;\n private readonly orphanCleanupIntervalMs: number;\n private readonly onProcessingError?: (event: {\n unprocessedEntity: Entity;\n errors: Error[];\n }) => Promise<void> | void;\n private readonly tracker: ProgressTracker;\n private readonly events: EventsService;\n\n private stopFunc?: () => void;\n\n constructor(options: {\n config: Config;\n scheduler: SchedulerService;\n logger: LoggerService;\n knex: Knex;\n processingDatabase: ProcessingDatabase;\n orchestrator: CatalogProcessingOrchestrator;\n stitcher: Stitcher;\n createHash: () => Hash;\n pollingIntervalMs?: number;\n orphanCleanupIntervalMs?: number;\n onProcessingError?: (event: {\n unprocessedEntity: Entity;\n errors: Error[];\n }) => Promise<void> | void;\n tracker?: ProgressTracker;\n events: EventsService;\n metrics: MetricsService;\n }) {\n this.config = options.config;\n this.scheduler = options.scheduler;\n this.logger = options.logger;\n this.knex = options.knex;\n this.processingDatabase = options.processingDatabase;\n this.orchestrator = options.orchestrator;\n this.stitcher = options.stitcher;\n this.createHash = options.createHash;\n this.pollingIntervalMs = options.pollingIntervalMs ?? 1_000;\n this.orphanCleanupIntervalMs = options.orphanCleanupIntervalMs ?? 30_000;\n this.onProcessingError = options.onProcessingError;\n this.tracker = options.tracker ?? progressTracker(options.metrics);\n this.events = options.events;\n\n this.stopFunc = undefined;\n }\n\n async start() {\n if (this.stopFunc) {\n throw new Error('Processing engine is already started');\n }\n\n const stopPipeline = this.startPipeline();\n const stopCleanup = this.startOrphanCleanup();\n\n this.stopFunc = () => {\n stopPipeline();\n stopCleanup();\n };\n }\n\n async stop() {\n if (this.stopFunc) {\n this.stopFunc();\n this.stopFunc = undefined;\n }\n }\n\n private startPipeline(): () => void {\n return startTaskPipeline<RefreshStateItem>({\n lowWatermark: 5,\n highWatermark: 10,\n pollingIntervalMs: this.pollingIntervalMs,\n loadTasks: async count => {\n try {\n const { items } = await this.processingDatabase.transaction(\n async tx => {\n return this.processingDatabase.getProcessableEntities(tx, {\n processBatchSize: count,\n });\n },\n );\n return items;\n } catch (error) {\n this.logger.warn('Failed to load processing items', error);\n return [];\n }\n },\n processTask: async item => {\n await withActiveSpan(tracer, 'ProcessingRun', async span => {\n const track = this.tracker.processStart(item, this.logger);\n addEntityAttributes(span, item.unprocessedEntity);\n\n try {\n const {\n id,\n state,\n unprocessedEntity,\n entityRef,\n locationKey,\n resultHash: previousResultHash,\n } = item;\n const result = await this.orchestrator.process({\n entity: unprocessedEntity,\n state,\n });\n\n track.markProcessorsCompleted(result);\n\n if (result.ok) {\n const { ttl: _, ...stateWithoutTtl } = state ?? {};\n if (\n stableStringify(stateWithoutTtl) !==\n stableStringify(result.state)\n ) {\n await this.processingDatabase.transaction(async tx => {\n await this.processingDatabase.updateEntityCache(tx, {\n id,\n state: {\n ttl: CACHE_TTL,\n ...result.state,\n },\n });\n });\n }\n } else {\n const maybeTtl = state?.ttl;\n const ttl = Number.isInteger(maybeTtl) ? (maybeTtl as number) : 0;\n await this.processingDatabase.transaction(async tx => {\n await this.processingDatabase.updateEntityCache(tx, {\n id,\n state: ttl > 0 ? { ...state, ttl: ttl - 1 } : {},\n });\n });\n }\n\n const location =\n unprocessedEntity?.metadata?.annotations?.[ANNOTATION_LOCATION];\n if (result.errors.length) {\n this.events.publish({\n topic: CATALOG_ERRORS_TOPIC,\n eventPayload: {\n entity: entityRef,\n location,\n errors: result.errors,\n },\n });\n }\n const errorsString = JSON.stringify(\n result.errors.map(e => serializeError(e)),\n );\n\n let hashBuilder = this.createHash().update(errorsString);\n\n if (result.ok) {\n const { entityRefs: parents } =\n await this.processingDatabase.transaction(tx =>\n this.processingDatabase.listParents(tx, {\n entityRefs: [\n entityRef,\n ...result.deferredEntities.map(e =>\n stringifyEntityRef(e.entity),\n ),\n ],\n }),\n );\n\n hashBuilder = hashBuilder\n .update(stableStringify({ ...result.completedEntity }))\n .update(stableStringifyArray([...result.deferredEntities]))\n .update(stableStringifyArray([...result.relations]))\n .update(stableStringifyArray([...result.refreshKeys]))\n .update(stableStringifyArray([...parents]));\n }\n\n const resultHash = hashBuilder.digest('hex');\n if (resultHash === previousResultHash) {\n // If nothing changed in our produced outputs, we cannot have any\n // significant effect on our surroundings; therefore, we just abort\n // without any updates / stitching.\n track.markSuccessfulWithNoChanges();\n return;\n }\n\n // If the result was marked as not OK, it signals that some part of the\n // processing pipeline threw an exception. This can happen both as part of\n // non-catastrophic things such as due to validation errors, as well as if\n // something fatal happens inside the processing for other reasons. In any\n // case, this means we can't trust that anything in the output is okay. So\n // just store the errors and trigger a stich so that they become visible to\n // the outside.\n if (!result.ok) {\n // notify the error listener if the entity can not be processed.\n Promise.resolve(undefined)\n .then(() =>\n this.onProcessingError?.({\n unprocessedEntity,\n errors: result.errors,\n }),\n )\n .catch(error => {\n this.logger.debug(\n `Processing error listener threw an exception, ${stringifyError(\n error,\n )}`,\n );\n });\n\n await this.processingDatabase.transaction(async tx => {\n await this.processingDatabase.updateProcessedEntityErrors(tx, {\n id,\n errors: errorsString,\n resultHash,\n });\n });\n\n await this.stitcher.stitch({\n entityRefs: [stringifyEntityRef(unprocessedEntity)],\n });\n\n track.markSuccessfulWithErrors();\n return;\n }\n\n result.completedEntity.metadata.uid = id;\n let oldRelationSources: Map<string, string>;\n await this.processingDatabase.transaction(async tx => {\n const { previous } =\n await this.processingDatabase.updateProcessedEntity(tx, {\n id,\n processedEntity: result.completedEntity,\n resultHash,\n errors: errorsString,\n relations: result.relations,\n deferredEntities: result.deferredEntities,\n locationKey,\n refreshKeys: result.refreshKeys,\n });\n oldRelationSources = new Map(\n previous.relations.map(r => [\n `${r.source_entity_ref}:${r.type}->${r.target_entity_ref}`,\n r.source_entity_ref,\n ]),\n );\n });\n\n const newRelationSources = new Map<string, string>(\n result.relations.map(relation => {\n const sourceEntityRef = stringifyEntityRef(relation.source);\n const targetEntityRef = stringifyEntityRef(relation.target);\n return [\n `${sourceEntityRef}:${relation.type}->${targetEntityRef}`,\n sourceEntityRef,\n ];\n }),\n );\n\n const setOfThingsToStitch = new Set<string>([\n stringifyEntityRef(result.completedEntity),\n ]);\n newRelationSources.forEach((sourceEntityRef, uniqueKey) => {\n if (!oldRelationSources.has(uniqueKey)) {\n setOfThingsToStitch.add(sourceEntityRef);\n }\n });\n oldRelationSources!.forEach((sourceEntityRef, uniqueKey) => {\n if (!newRelationSources.has(uniqueKey)) {\n setOfThingsToStitch.add(sourceEntityRef);\n }\n });\n\n await this.stitcher.stitch({\n entityRefs: setOfThingsToStitch,\n });\n\n track.markSuccessfulWithChanges();\n } catch (error) {\n assertError(error);\n track.markFailed(error);\n }\n });\n },\n });\n }\n\n private startOrphanCleanup(): () => void {\n const orphanStrategy =\n this.config.getOptionalString('catalog.orphanStrategy') ?? 'delete';\n if (orphanStrategy !== 'delete') {\n return () => {};\n }\n\n const stitchingStrategy = stitchingStrategyFromConfig(this.config);\n\n const runOnce = async () => {\n try {\n const n = await deleteOrphanedEntities({\n knex: this.knex,\n strategy: stitchingStrategy,\n });\n if (n > 0) {\n this.logger.info(`Deleted ${n} orphaned entities`);\n }\n } catch (error) {\n this.logger.warn(`Failed to delete orphaned entities`, error);\n }\n };\n\n const abortController = new AbortController();\n this.scheduler.scheduleTask({\n id: 'catalog_orphan_cleanup',\n frequency: { milliseconds: this.orphanCleanupIntervalMs },\n timeout: { milliseconds: this.orphanCleanupIntervalMs * 0.8 },\n fn: runOnce,\n signal: abortController.signal,\n });\n\n return () => {\n abortController.abort();\n };\n }\n}\n\n// Helps wrap the timing and logging behaviors\nfunction progressTracker(metrics: MetricsService) {\n // prom-client metrics are deprecated in favour of OpenTelemetry metrics.\n const promProcessedEntities = createCounterMetric({\n name: 'catalog_processed_entities_count',\n help: 'Amount of entities processed, DEPRECATED, use OpenTelemetry metrics instead',\n labelNames: ['result'],\n });\n const promProcessingDuration = createSummaryMetric({\n name: 'catalog_processing_duration_seconds',\n help: 'Time spent executing the full processing flow, DEPRECATED, use OpenTelemetry metrics instead',\n labelNames: ['result'],\n });\n const promProcessorsDuration = createSummaryMetric({\n name: 'catalog_processors_duration_seconds',\n help: 'Time spent executing catalog processors, DEPRECATED, use OpenTelemetry metrics instead',\n labelNames: ['result'],\n });\n const promProcessingQueueDelay = createSummaryMetric({\n name: 'catalog_processing_queue_delay_seconds',\n help: 'The amount of delay between being scheduled for processing, and the start of actually being processed, DEPRECATED, use OpenTelemetry metrics instead',\n });\n\n const processedEntities = metrics.createCounter(\n 'catalog.processed.entities.count',\n { description: 'Amount of entities processed' },\n );\n\n const processingDuration = metrics.createHistogram(\n 'catalog.processing.duration',\n {\n description: 'Time spent executing the full processing flow',\n unit: 'seconds',\n },\n );\n\n const processorsDuration = metrics.createHistogram(\n 'catalog.processors.duration',\n {\n description: 'Time spent executing catalog processors',\n unit: 'seconds',\n },\n );\n\n const processingQueueDelay = metrics.createHistogram(\n 'catalog.processing.queue.delay',\n {\n description:\n 'The amount of delay between being scheduled for processing, and the start of actually being processed',\n unit: 'seconds',\n },\n );\n\n function processStart(item: RefreshStateItem, logger: LoggerService) {\n const startTime = process.hrtime();\n const endOverallTimer = promProcessingDuration.startTimer();\n const endProcessorsTimer = promProcessorsDuration.startTimer();\n\n logger.debug(`Processing ${item.entityRef}`);\n\n if (item.nextUpdateAt) {\n const seconds = -item.nextUpdateAt.diffNow().as('seconds');\n promProcessingQueueDelay.observe(seconds);\n processingQueueDelay.record(seconds);\n }\n\n function endTime() {\n const delta = process.hrtime(startTime);\n return delta[0] + delta[1] / 1e9;\n }\n\n function markProcessorsCompleted(result: EntityProcessingResult) {\n endProcessorsTimer({ result: result.ok ? 'ok' : 'failed' });\n processorsDuration.record(endTime(), {\n result: result.ok ? 'ok' : 'failed',\n });\n }\n\n function markSuccessfulWithNoChanges() {\n endOverallTimer({ result: 'unchanged' });\n promProcessedEntities.inc({ result: 'unchanged' }, 1);\n\n processingDuration.record(endTime(), { result: 'unchanged' });\n processedEntities.add(1, { result: 'unchanged' });\n }\n\n function markSuccessfulWithErrors() {\n endOverallTimer({ result: 'errors' });\n promProcessedEntities.inc({ result: 'errors' }, 1);\n\n processingDuration.record(endTime(), { result: 'errors' });\n processedEntities.add(1, { result: 'errors' });\n }\n\n function markSuccessfulWithChanges() {\n endOverallTimer({ result: 'changed' });\n promProcessedEntities.inc({ result: 'changed' }, 1);\n\n processingDuration.record(endTime(), { result: 'changed' });\n processedEntities.add(1, { result: 'changed' });\n }\n\n function markFailed(error: Error) {\n promProcessedEntities.inc({ result: 'failed' }, 1);\n processedEntities.add(1, { result: 'failed' });\n logger.warn(`Processing of ${item.entityRef} failed`, error);\n }\n\n return {\n markProcessorsCompleted,\n markSuccessfulWithNoChanges,\n markSuccessfulWithErrors,\n markSuccessfulWithChanges,\n markFailed,\n };\n }\n\n return { processStart };\n}\n"],"names":["trace","TRACER_ID","stableStringify","startTaskPipeline","withActiveSpan","addEntityAttributes","ANNOTATION_LOCATION","CATALOG_ERRORS_TOPIC","serializeError","stringifyEntityRef","stringifyError","assertError","stitchingStrategyFromConfig","deleteOrphanedEntities","metrics","createCounterMetric","createSummaryMetric"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DefaultCatalogProcessingEngine.cjs.js","sources":["../../src/processing/DefaultCatalogProcessingEngine.ts"],"sourcesContent":["/*\n * Copyright 2021 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 {\n ANNOTATION_LOCATION,\n Entity,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { serializeError, stringifyError, toError } from '@backstage/errors';\nimport { Hash } from 'node:crypto';\nimport stableStringify from 'fast-json-stable-stringify';\nimport { Knex } from 'knex';\nimport { trace } from '@opentelemetry/api';\nimport { ProcessingDatabase, RefreshStateItem } from '../database/types';\nimport { createCounterMetric, createSummaryMetric } from '../util/metrics';\nimport { CatalogProcessingOrchestrator, EntityProcessingResult } from './types';\nimport { Stitcher, stitchingStrategyFromConfig } from '../stitching/types';\nimport { startTaskPipeline } from './TaskPipeline';\nimport { Config } from '@backstage/config';\nimport {\n addEntityAttributes,\n TRACER_ID,\n withActiveSpan,\n} from '../util/opentelemetry';\nimport { deleteOrphanedEntities } from '../database/operations/util/deleteOrphanedEntities';\nimport { EventsService } from '@backstage/plugin-events-node';\nimport { CATALOG_ERRORS_TOPIC } from '../constants';\nimport { LoggerService, SchedulerService } from '@backstage/backend-plugin-api';\nimport { MetricsService } from '@backstage/backend-plugin-api/alpha';\n\nconst CACHE_TTL = 5;\n\nconst tracer = trace.getTracer(TRACER_ID);\n\nexport type ProgressTracker = ReturnType<typeof progressTracker>;\n\nconst stableStringifyArray = (arr: any[]) => {\n const sorted = arr.map(stableStringify).sort();\n return `[${sorted.join(',')}]`;\n};\n\n// NOTE(freben): Perhaps surprisingly, this class does not implement the\n// CatalogProcessingEngine type. That type is externally visible and its name is\n// the way it is for historic reasons. This class has no particular reason to\n// implement that precise interface; nowadays there are several different\n// engines \"hiding\" behind the CatalogProcessingEngine interface, of which this\n// is just one.\nexport class DefaultCatalogProcessingEngine {\n private readonly config: Config;\n private readonly scheduler: SchedulerService;\n private readonly logger: LoggerService;\n private readonly knex: Knex;\n private readonly processingDatabase: ProcessingDatabase;\n private readonly orchestrator: CatalogProcessingOrchestrator;\n private readonly stitcher: Stitcher;\n private readonly createHash: () => Hash;\n private readonly pollingIntervalMs: number;\n private readonly orphanCleanupIntervalMs: number;\n private readonly onProcessingError?: (event: {\n unprocessedEntity: Entity;\n errors: Error[];\n }) => Promise<void> | void;\n private readonly tracker: ProgressTracker;\n private readonly events: EventsService;\n\n private stopFunc?: () => void;\n\n constructor(options: {\n config: Config;\n scheduler: SchedulerService;\n logger: LoggerService;\n knex: Knex;\n processingDatabase: ProcessingDatabase;\n orchestrator: CatalogProcessingOrchestrator;\n stitcher: Stitcher;\n createHash: () => Hash;\n pollingIntervalMs?: number;\n orphanCleanupIntervalMs?: number;\n onProcessingError?: (event: {\n unprocessedEntity: Entity;\n errors: Error[];\n }) => Promise<void> | void;\n tracker?: ProgressTracker;\n events: EventsService;\n metrics: MetricsService;\n }) {\n this.config = options.config;\n this.scheduler = options.scheduler;\n this.logger = options.logger;\n this.knex = options.knex;\n this.processingDatabase = options.processingDatabase;\n this.orchestrator = options.orchestrator;\n this.stitcher = options.stitcher;\n this.createHash = options.createHash;\n this.pollingIntervalMs = options.pollingIntervalMs ?? 1_000;\n this.orphanCleanupIntervalMs = options.orphanCleanupIntervalMs ?? 30_000;\n this.onProcessingError = options.onProcessingError;\n this.tracker = options.tracker ?? progressTracker(options.metrics);\n this.events = options.events;\n\n this.stopFunc = undefined;\n }\n\n async start() {\n if (this.stopFunc) {\n throw new Error('Processing engine is already started');\n }\n\n const stopPipeline = this.startPipeline();\n const stopCleanup = this.startOrphanCleanup();\n\n this.stopFunc = () => {\n stopPipeline();\n stopCleanup();\n };\n }\n\n async stop() {\n if (this.stopFunc) {\n this.stopFunc();\n this.stopFunc = undefined;\n }\n }\n\n private startPipeline(): () => void {\n return startTaskPipeline<RefreshStateItem>({\n lowWatermark: 5,\n highWatermark: 10,\n pollingIntervalMs: this.pollingIntervalMs,\n loadTasks: async count => {\n try {\n const { items } = await this.processingDatabase.transaction(\n async tx => {\n return this.processingDatabase.getProcessableEntities(tx, {\n processBatchSize: count,\n });\n },\n );\n return items;\n } catch (error) {\n this.logger.warn('Failed to load processing items', error);\n return [];\n }\n },\n processTask: async item => {\n await withActiveSpan(tracer, 'ProcessingRun', async span => {\n const track = this.tracker.processStart(item, this.logger);\n addEntityAttributes(span, item.unprocessedEntity);\n\n try {\n const {\n id,\n state,\n unprocessedEntity,\n entityRef,\n locationKey,\n resultHash: previousResultHash,\n } = item;\n const result = await this.orchestrator.process({\n entity: unprocessedEntity,\n state,\n });\n\n track.markProcessorsCompleted(result);\n\n if (result.ok) {\n const { ttl: _, ...stateWithoutTtl } = state ?? {};\n if (\n stableStringify(stateWithoutTtl) !==\n stableStringify(result.state)\n ) {\n await this.processingDatabase.transaction(async tx => {\n await this.processingDatabase.updateEntityCache(tx, {\n id,\n state: {\n ttl: CACHE_TTL,\n ...result.state,\n },\n });\n });\n }\n } else {\n const maybeTtl = state?.ttl;\n const ttl = Number.isInteger(maybeTtl) ? (maybeTtl as number) : 0;\n await this.processingDatabase.transaction(async tx => {\n await this.processingDatabase.updateEntityCache(tx, {\n id,\n state: ttl > 0 ? { ...state, ttl: ttl - 1 } : {},\n });\n });\n }\n\n const location =\n unprocessedEntity?.metadata?.annotations?.[ANNOTATION_LOCATION];\n if (result.errors.length) {\n this.events.publish({\n topic: CATALOG_ERRORS_TOPIC,\n eventPayload: {\n entity: entityRef,\n location,\n errors: result.errors,\n },\n });\n }\n const errorsString = JSON.stringify(\n result.errors.map(e => serializeError(e)),\n );\n\n let hashBuilder = this.createHash().update(errorsString);\n\n if (result.ok) {\n const { entityRefs: parents } =\n await this.processingDatabase.transaction(tx =>\n this.processingDatabase.listParents(tx, {\n entityRefs: [\n entityRef,\n ...result.deferredEntities.map(e =>\n stringifyEntityRef(e.entity),\n ),\n ],\n }),\n );\n\n hashBuilder = hashBuilder\n .update(stableStringify({ ...result.completedEntity }))\n .update(stableStringifyArray([...result.deferredEntities]))\n .update(stableStringifyArray([...result.relations]))\n .update(stableStringifyArray([...result.refreshKeys]))\n .update(stableStringifyArray([...parents]));\n }\n\n const resultHash = hashBuilder.digest('hex');\n if (resultHash === previousResultHash) {\n // If nothing changed in our produced outputs, we cannot have any\n // significant effect on our surroundings; therefore, we just abort\n // without any updates / stitching.\n track.markSuccessfulWithNoChanges();\n return;\n }\n\n // If the result was marked as not OK, it signals that some part of the\n // processing pipeline threw an exception. This can happen both as part of\n // non-catastrophic things such as due to validation errors, as well as if\n // something fatal happens inside the processing for other reasons. In any\n // case, this means we can't trust that anything in the output is okay. So\n // just store the errors and trigger a stich so that they become visible to\n // the outside.\n if (!result.ok) {\n // notify the error listener if the entity can not be processed.\n Promise.resolve(undefined)\n .then(() =>\n this.onProcessingError?.({\n unprocessedEntity,\n errors: result.errors,\n }),\n )\n .catch(error => {\n this.logger.debug(\n `Processing error listener threw an exception, ${stringifyError(\n error,\n )}`,\n );\n });\n\n await this.processingDatabase.transaction(async tx => {\n await this.processingDatabase.updateProcessedEntityErrors(tx, {\n id,\n errors: errorsString,\n resultHash,\n });\n });\n\n await this.stitcher.stitch({\n entityRefs: [stringifyEntityRef(unprocessedEntity)],\n });\n\n track.markSuccessfulWithErrors();\n return;\n }\n\n result.completedEntity.metadata.uid = id;\n let oldRelationSources: Map<string, string>;\n await this.processingDatabase.transaction(async tx => {\n const { previous } =\n await this.processingDatabase.updateProcessedEntity(tx, {\n id,\n processedEntity: result.completedEntity,\n resultHash,\n errors: errorsString,\n relations: result.relations,\n deferredEntities: result.deferredEntities,\n locationKey,\n refreshKeys: result.refreshKeys,\n });\n oldRelationSources = new Map(\n previous.relations.map(r => [\n `${r.source_entity_ref}:${r.type}->${r.target_entity_ref}`,\n r.source_entity_ref,\n ]),\n );\n });\n\n const newRelationSources = new Map<string, string>(\n result.relations.map(relation => {\n const sourceEntityRef = stringifyEntityRef(relation.source);\n const targetEntityRef = stringifyEntityRef(relation.target);\n return [\n `${sourceEntityRef}:${relation.type}->${targetEntityRef}`,\n sourceEntityRef,\n ];\n }),\n );\n\n const setOfThingsToStitch = new Set<string>([\n stringifyEntityRef(result.completedEntity),\n ]);\n newRelationSources.forEach((sourceEntityRef, uniqueKey) => {\n if (!oldRelationSources.has(uniqueKey)) {\n setOfThingsToStitch.add(sourceEntityRef);\n }\n });\n oldRelationSources!.forEach((sourceEntityRef, uniqueKey) => {\n if (!newRelationSources.has(uniqueKey)) {\n setOfThingsToStitch.add(sourceEntityRef);\n }\n });\n\n await this.stitcher.stitch({\n entityRefs: setOfThingsToStitch,\n });\n\n track.markSuccessfulWithChanges();\n } catch (error) {\n track.markFailed(toError(error));\n }\n });\n },\n });\n }\n\n private startOrphanCleanup(): () => void {\n const orphanStrategy =\n this.config.getOptionalString('catalog.orphanStrategy') ?? 'delete';\n if (orphanStrategy !== 'delete') {\n return () => {};\n }\n\n const stitchingStrategy = stitchingStrategyFromConfig(this.config);\n\n const runOnce = async () => {\n try {\n const n = await deleteOrphanedEntities({\n knex: this.knex,\n strategy: stitchingStrategy,\n });\n if (n > 0) {\n this.logger.info(`Deleted ${n} orphaned entities`);\n }\n } catch (error) {\n this.logger.warn(`Failed to delete orphaned entities`, error);\n }\n };\n\n const abortController = new AbortController();\n this.scheduler.scheduleTask({\n id: 'catalog_orphan_cleanup',\n frequency: { milliseconds: this.orphanCleanupIntervalMs },\n timeout: { milliseconds: this.orphanCleanupIntervalMs * 0.8 },\n fn: runOnce,\n signal: abortController.signal,\n });\n\n return () => {\n abortController.abort();\n };\n }\n}\n\n// Helps wrap the timing and logging behaviors\nfunction progressTracker(metrics: MetricsService) {\n // prom-client metrics are deprecated in favour of OpenTelemetry metrics.\n const promProcessedEntities = createCounterMetric({\n name: 'catalog_processed_entities_count',\n help: 'Amount of entities processed, DEPRECATED, use OpenTelemetry metrics instead',\n labelNames: ['result'],\n });\n const promProcessingDuration = createSummaryMetric({\n name: 'catalog_processing_duration_seconds',\n help: 'Time spent executing the full processing flow, DEPRECATED, use OpenTelemetry metrics instead',\n labelNames: ['result'],\n });\n const promProcessorsDuration = createSummaryMetric({\n name: 'catalog_processors_duration_seconds',\n help: 'Time spent executing catalog processors, DEPRECATED, use OpenTelemetry metrics instead',\n labelNames: ['result'],\n });\n const promProcessingQueueDelay = createSummaryMetric({\n name: 'catalog_processing_queue_delay_seconds',\n help: 'The amount of delay between being scheduled for processing, and the start of actually being processed, DEPRECATED, use OpenTelemetry metrics instead',\n });\n\n const processedEntities = metrics.createCounter(\n 'catalog.processed.entities.count',\n { description: 'Amount of entities processed' },\n );\n\n const processingDuration = metrics.createHistogram(\n 'catalog.processing.duration',\n {\n description: 'Time spent executing the full processing flow',\n unit: 'seconds',\n },\n );\n\n const processorsDuration = metrics.createHistogram(\n 'catalog.processors.duration',\n {\n description: 'Time spent executing catalog processors',\n unit: 'seconds',\n },\n );\n\n const processingQueueDelay = metrics.createHistogram(\n 'catalog.processing.queue.delay',\n {\n description:\n 'The amount of delay between being scheduled for processing, and the start of actually being processed',\n unit: 'seconds',\n },\n );\n\n function processStart(item: RefreshStateItem, logger: LoggerService) {\n const startTime = process.hrtime();\n const endOverallTimer = promProcessingDuration.startTimer();\n const endProcessorsTimer = promProcessorsDuration.startTimer();\n\n logger.debug(`Processing ${item.entityRef}`);\n\n if (item.nextUpdateAt) {\n const seconds = -item.nextUpdateAt.diffNow().as('seconds');\n promProcessingQueueDelay.observe(seconds);\n processingQueueDelay.record(seconds);\n }\n\n function endTime() {\n const delta = process.hrtime(startTime);\n return delta[0] + delta[1] / 1e9;\n }\n\n function markProcessorsCompleted(result: EntityProcessingResult) {\n endProcessorsTimer({ result: result.ok ? 'ok' : 'failed' });\n processorsDuration.record(endTime(), {\n result: result.ok ? 'ok' : 'failed',\n });\n }\n\n function markSuccessfulWithNoChanges() {\n endOverallTimer({ result: 'unchanged' });\n promProcessedEntities.inc({ result: 'unchanged' }, 1);\n\n processingDuration.record(endTime(), { result: 'unchanged' });\n processedEntities.add(1, { result: 'unchanged' });\n }\n\n function markSuccessfulWithErrors() {\n endOverallTimer({ result: 'errors' });\n promProcessedEntities.inc({ result: 'errors' }, 1);\n\n processingDuration.record(endTime(), { result: 'errors' });\n processedEntities.add(1, { result: 'errors' });\n }\n\n function markSuccessfulWithChanges() {\n endOverallTimer({ result: 'changed' });\n promProcessedEntities.inc({ result: 'changed' }, 1);\n\n processingDuration.record(endTime(), { result: 'changed' });\n processedEntities.add(1, { result: 'changed' });\n }\n\n function markFailed(error: Error) {\n promProcessedEntities.inc({ result: 'failed' }, 1);\n processedEntities.add(1, { result: 'failed' });\n logger.warn(`Processing of ${item.entityRef} failed`, error);\n }\n\n return {\n markProcessorsCompleted,\n markSuccessfulWithNoChanges,\n markSuccessfulWithErrors,\n markSuccessfulWithChanges,\n markFailed,\n };\n }\n\n return { processStart };\n}\n"],"names":["trace","TRACER_ID","stableStringify","startTaskPipeline","withActiveSpan","addEntityAttributes","ANNOTATION_LOCATION","CATALOG_ERRORS_TOPIC","serializeError","stringifyEntityRef","stringifyError","toError","stitchingStrategyFromConfig","deleteOrphanedEntities","metrics","createCounterMetric","createSummaryMetric"],"mappings":"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"}
|
|
@@ -86,10 +86,10 @@ class DefaultCatalogProcessingOrchestrator {
|
|
|
86
86
|
ok: collectorResults.errors.length === 0
|
|
87
87
|
};
|
|
88
88
|
} catch (error) {
|
|
89
|
-
errors.
|
|
89
|
+
const err = errors.toError(error);
|
|
90
90
|
return {
|
|
91
91
|
ok: false,
|
|
92
|
-
errors: collector.results().errors.concat(
|
|
92
|
+
errors: collector.results().errors.concat(err)
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultCatalogProcessingOrchestrator.cjs.js","sources":["../../src/processing/DefaultCatalogProcessingOrchestrator.ts"],"sourcesContent":["/*\n * Copyright 2021 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 { Span, trace } from '@opentelemetry/api';\nimport {\n Entity,\n EntityPolicy,\n LocationEntity,\n parseLocationRef,\n stringifyEntityRef,\n stringifyLocationRef,\n} from '@backstage/catalog-model';\nimport {\n assertError,\n ConflictError,\n InputError,\n NotAllowedError,\n} from '@backstage/errors';\nimport { JsonValue } from '@backstage/types';\nimport { ScmIntegrationRegistry } from '@backstage/integration';\nimport path from 'node:path';\nimport { LocationSpec } from '@backstage/plugin-catalog-common';\nimport {\n CatalogProcessor,\n CatalogProcessorParser,\n processingResult,\n} from '@backstage/plugin-catalog-node';\nimport {\n CatalogProcessingOrchestrator,\n EntityProcessingRequest,\n EntityProcessingResult,\n} from './types';\nimport { ProcessorOutputCollector } from './ProcessorOutputCollector';\nimport {\n getEntityLocationRef,\n getEntityOriginLocationRef,\n isLocationEntity,\n isObject,\n toAbsoluteUrl,\n validateEntity,\n validateEntityEnvelope,\n} from './util';\nimport { CatalogRulesEnforcer } from '../ingestion/CatalogRules';\nimport { ProcessorCacheManager } from './ProcessorCacheManager';\nimport {\n addEntityAttributes,\n TRACER_ID,\n withActiveSpan,\n} from '../util/opentelemetry';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nconst tracer = trace.getTracer(TRACER_ID);\n\ntype Context = {\n entityRef: string;\n location: LocationSpec;\n originLocation: LocationSpec;\n collector: ProcessorOutputCollector;\n cache: ProcessorCacheManager;\n};\n\nfunction addProcessorAttributes(\n span: Span,\n stage: string,\n processor: CatalogProcessor,\n) {\n span.setAttribute('backstage.catalog.processor.stage', stage);\n span.setAttribute(\n 'backstage.catalog.processor.name',\n processor.getProcessorName(),\n );\n}\n\nexport class DefaultCatalogProcessingOrchestrator\n implements CatalogProcessingOrchestrator\n{\n private readonly options: {\n processors: CatalogProcessor[];\n integrations: ScmIntegrationRegistry;\n logger: LoggerService;\n parser: CatalogProcessorParser;\n policy: EntityPolicy;\n rulesEnforcer: CatalogRulesEnforcer;\n };\n\n constructor(options: {\n processors: CatalogProcessor[];\n integrations: ScmIntegrationRegistry;\n logger: LoggerService;\n parser: CatalogProcessorParser;\n policy: EntityPolicy;\n rulesEnforcer: CatalogRulesEnforcer;\n }) {\n this.options = options;\n }\n\n async process(\n request: EntityProcessingRequest,\n ): Promise<EntityProcessingResult> {\n return this.processSingleEntity(request.entity, request.state);\n }\n\n private async processSingleEntity(\n unprocessedEntity: Entity,\n state: JsonValue | undefined,\n ): Promise<EntityProcessingResult> {\n const collector = new ProcessorOutputCollector(\n this.options.logger,\n unprocessedEntity,\n );\n\n // Cache that is scoped to the entity and processor\n const cache = new ProcessorCacheManager(\n isObject(state) && isObject(state.cache) ? state.cache : {},\n );\n\n try {\n // This will be checked and mutated step by step below\n let entity: Entity = unprocessedEntity;\n\n // NOTE: At this early point, we can only rely on the envelope having to\n // be valid; full entity + kind validation happens after the (potentially\n // mutative) pre-steps. This means that the code below can't make a lot\n // of assumptions about the data despite it using the Entity type.\n try {\n validateEntityEnvelope(entity);\n } catch (e) {\n throw new InputError(\n `Entity envelope failed validation before processing`,\n e,\n );\n }\n\n // TODO: which one do we actually use for the location?\n // source-location? - maybe probably doesn't exist yet?\n const context: Context = {\n entityRef: stringifyEntityRef(entity),\n location: parseLocationRef(getEntityLocationRef(entity)),\n originLocation: parseLocationRef(getEntityOriginLocationRef(entity)),\n cache,\n collector,\n };\n\n // Run the steps\n entity = await this.runPreProcessStep(entity, context);\n entity = await this.runPolicyStep(entity);\n await this.runValidateStep(entity, context);\n if (isLocationEntity(entity)) {\n await this.runSpecialLocationStep(entity, context);\n }\n entity = await this.runPostProcessStep(entity, context);\n\n // Check that any emitted entities are permitted to originate from that\n // particular location according to the catalog rules\n const collectorResults = context.collector.results();\n for (const deferredEntity of collectorResults.deferredEntities) {\n if (\n !this.options.rulesEnforcer.isAllowed(\n deferredEntity.entity,\n context.originLocation,\n )\n ) {\n throw new NotAllowedError(\n `Entity ${stringifyEntityRef(\n deferredEntity.entity,\n )} at ${stringifyLocationRef(\n context.location,\n )}, originated at ${stringifyLocationRef(\n context.originLocation,\n )}, is not of an allowed kind for that location`,\n );\n }\n }\n\n return {\n ...collectorResults,\n completedEntity: entity,\n state: { cache: cache.collect() },\n ok: collectorResults.errors.length === 0,\n };\n } catch (error) {\n assertError(error);\n return {\n ok: false,\n errors: collector.results().errors.concat(error),\n };\n }\n }\n\n // Pre-process phase, used to populate entities with data that is required\n // during the main processing step\n private async runPreProcessStep(\n entity: Entity,\n context: Context,\n ): Promise<Entity> {\n return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {\n addEntityAttributes(stageSpan, entity);\n stageSpan.setAttribute('backstage.catalog.processor.stage', 'preProcess');\n let res = entity;\n\n for (const processor of this.options.processors) {\n if (processor.preProcessEntity) {\n let innerRes = res;\n res = await withActiveSpan(tracer, 'ProcessingStep', async span => {\n addEntityAttributes(span, entity);\n addProcessorAttributes(span, 'preProcessEntity', processor);\n try {\n innerRes = await processor.preProcessEntity!(\n innerRes,\n context.location,\n context.collector.forProcessor(processor),\n context.originLocation,\n context.cache.forProcessor(processor),\n );\n } catch (e) {\n throw new InputError(\n `Processor ${processor.constructor.name} threw an error while preprocessing`,\n e,\n );\n }\n return innerRes;\n });\n }\n }\n\n return res;\n });\n }\n\n /**\n * Enforce entity policies making sure that entities conform to a general schema\n */\n private async runPolicyStep(entity: Entity): Promise<Entity> {\n return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {\n addEntityAttributes(stageSpan, entity);\n stageSpan.setAttribute(\n 'backstage.catalog.processor.stage',\n 'enforcePolicy',\n );\n let policyEnforcedEntity: Entity | undefined;\n\n try {\n policyEnforcedEntity = await this.options.policy.enforce(entity);\n } catch (e) {\n throw new InputError(\n `Policy check failed for ${stringifyEntityRef(entity)}`,\n e,\n );\n }\n\n if (!policyEnforcedEntity) {\n throw new Error(\n `Policy unexpectedly returned no data for ${stringifyEntityRef(\n entity,\n )}`,\n );\n }\n\n return policyEnforcedEntity;\n });\n }\n\n /**\n * Validate the given entity\n */\n private async runValidateStep(\n entity: Entity,\n context: Context,\n ): Promise<void> {\n return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {\n addEntityAttributes(stageSpan, entity);\n stageSpan.setAttribute('backstage.catalog.processor.stage', 'validate');\n // Double check that none of the previous steps tried to change something\n // related to the entity ref, which would break downstream\n if (stringifyEntityRef(entity) !== context.entityRef) {\n throw new ConflictError(\n 'Fatal: The entity kind, namespace, or name changed during processing',\n );\n }\n\n // Validate that the end result is a valid Entity at all\n try {\n validateEntity(entity);\n } catch (e) {\n throw new ConflictError(\n `Entity envelope for ${context.entityRef} failed validation after preprocessing`,\n e,\n );\n }\n\n let valid = false;\n\n for (const processor of this.options.processors) {\n if (processor.validateEntityKind) {\n try {\n const thisValid = await withActiveSpan(\n tracer,\n 'ProcessingStep',\n async span => {\n addEntityAttributes(span, entity);\n addProcessorAttributes(span, 'validateEntityKind', processor);\n return await processor.validateEntityKind!(entity);\n },\n );\n if (thisValid) {\n valid = true;\n }\n } catch (e) {\n throw new InputError(\n `Processor ${processor.constructor.name} threw an error while validating the entity ${context.entityRef}`,\n e,\n );\n }\n }\n }\n\n if (!valid) {\n throw new InputError(\n `No processor recognized the entity ${context.entityRef} as valid, possibly caused by a foreign kind or apiVersion`,\n );\n }\n });\n }\n\n /**\n * Backwards compatible processing of location entities\n */\n private async runSpecialLocationStep(\n entity: LocationEntity,\n context: Context,\n ): Promise<void> {\n return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {\n addEntityAttributes(stageSpan, entity);\n stageSpan.setAttribute(\n 'backstage.catalog.processor.stage',\n 'readLocation',\n );\n const { type = context.location.type, presence = 'required' } =\n entity.spec;\n const targets = new Array<string>();\n if (entity.spec.target) {\n targets.push(entity.spec.target);\n }\n if (entity.spec.targets) {\n targets.push(...entity.spec.targets);\n }\n\n for (const maybeRelativeTarget of targets) {\n if (type === 'file' && maybeRelativeTarget.endsWith(path.sep)) {\n context.collector.generic()(\n processingResult.inputError(\n context.location,\n `LocationEntityProcessor cannot handle ${type} type location with target ${context.location.target} that ends with a path separator`,\n ),\n );\n continue;\n }\n const target = toAbsoluteUrl(\n this.options.integrations,\n context.location,\n type,\n maybeRelativeTarget,\n );\n\n let didRead = false;\n for (const processor of this.options.processors) {\n if (processor.readLocation) {\n try {\n const read = await withActiveSpan(\n tracer,\n 'ProcessingStep',\n async span => {\n addEntityAttributes(span, entity);\n addProcessorAttributes(span, 'readLocation', processor);\n return await processor.readLocation!(\n {\n type,\n target,\n presence,\n },\n presence === 'optional',\n context.collector.forProcessor(processor),\n this.options.parser,\n context.cache.forProcessor(processor, target),\n );\n },\n );\n if (read) {\n didRead = true;\n break;\n }\n } catch (e) {\n throw new InputError(\n `Processor ${processor.constructor.name} threw an error while reading ${type}:${target}`,\n e,\n );\n }\n }\n }\n if (!didRead) {\n throw new InputError(\n `No processor was able to handle reading of ${type}:${target}`,\n );\n }\n }\n });\n }\n\n /**\n * Main processing step of the entity\n */\n private async runPostProcessStep(\n entity: Entity,\n context: Context,\n ): Promise<Entity> {\n return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {\n addEntityAttributes(stageSpan, entity);\n stageSpan.setAttribute(\n 'backstage.catalog.processor.stage',\n 'postProcessEntity',\n );\n let res = entity;\n\n for (const processor of this.options.processors) {\n if (processor.postProcessEntity) {\n let innerRes = res;\n res = await withActiveSpan(tracer, 'ProcessingStep', async span => {\n addEntityAttributes(span, entity);\n addProcessorAttributes(span, 'postProcessEntity', processor);\n try {\n innerRes = await processor.postProcessEntity!(\n innerRes,\n context.location,\n context.collector.forProcessor(processor),\n context.cache.forProcessor(processor),\n );\n } catch (e) {\n throw new InputError(\n `Processor ${processor.constructor.name} threw an error while postprocessing`,\n e,\n );\n }\n return innerRes;\n });\n }\n }\n\n return res;\n });\n }\n}\n"],"names":["trace","TRACER_ID","ProcessorOutputCollector","ProcessorCacheManager","isObject","validateEntityEnvelope","InputError","stringifyEntityRef","parseLocationRef","getEntityLocationRef","getEntityOriginLocationRef","isLocationEntity","NotAllowedError","stringifyLocationRef","assertError","withActiveSpan","addEntityAttributes","ConflictError","validateEntity","path","processingResult","toAbsoluteUrl"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DefaultCatalogProcessingOrchestrator.cjs.js","sources":["../../src/processing/DefaultCatalogProcessingOrchestrator.ts"],"sourcesContent":["/*\n * Copyright 2021 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 { Span, trace } from '@opentelemetry/api';\nimport {\n Entity,\n EntityPolicy,\n LocationEntity,\n parseLocationRef,\n stringifyEntityRef,\n stringifyLocationRef,\n} from '@backstage/catalog-model';\nimport {\n ConflictError,\n InputError,\n NotAllowedError,\n toError,\n} from '@backstage/errors';\nimport { JsonValue } from '@backstage/types';\nimport { ScmIntegrationRegistry } from '@backstage/integration';\nimport path from 'node:path';\nimport { LocationSpec } from '@backstage/plugin-catalog-common';\nimport {\n CatalogProcessor,\n CatalogProcessorParser,\n processingResult,\n} from '@backstage/plugin-catalog-node';\nimport {\n CatalogProcessingOrchestrator,\n EntityProcessingRequest,\n EntityProcessingResult,\n} from './types';\nimport { ProcessorOutputCollector } from './ProcessorOutputCollector';\nimport {\n getEntityLocationRef,\n getEntityOriginLocationRef,\n isLocationEntity,\n isObject,\n toAbsoluteUrl,\n validateEntity,\n validateEntityEnvelope,\n} from './util';\nimport { CatalogRulesEnforcer } from '../ingestion/CatalogRules';\nimport { ProcessorCacheManager } from './ProcessorCacheManager';\nimport {\n addEntityAttributes,\n TRACER_ID,\n withActiveSpan,\n} from '../util/opentelemetry';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nconst tracer = trace.getTracer(TRACER_ID);\n\ntype Context = {\n entityRef: string;\n location: LocationSpec;\n originLocation: LocationSpec;\n collector: ProcessorOutputCollector;\n cache: ProcessorCacheManager;\n};\n\nfunction addProcessorAttributes(\n span: Span,\n stage: string,\n processor: CatalogProcessor,\n) {\n span.setAttribute('backstage.catalog.processor.stage', stage);\n span.setAttribute(\n 'backstage.catalog.processor.name',\n processor.getProcessorName(),\n );\n}\n\nexport class DefaultCatalogProcessingOrchestrator\n implements CatalogProcessingOrchestrator\n{\n private readonly options: {\n processors: CatalogProcessor[];\n integrations: ScmIntegrationRegistry;\n logger: LoggerService;\n parser: CatalogProcessorParser;\n policy: EntityPolicy;\n rulesEnforcer: CatalogRulesEnforcer;\n };\n\n constructor(options: {\n processors: CatalogProcessor[];\n integrations: ScmIntegrationRegistry;\n logger: LoggerService;\n parser: CatalogProcessorParser;\n policy: EntityPolicy;\n rulesEnforcer: CatalogRulesEnforcer;\n }) {\n this.options = options;\n }\n\n async process(\n request: EntityProcessingRequest,\n ): Promise<EntityProcessingResult> {\n return this.processSingleEntity(request.entity, request.state);\n }\n\n private async processSingleEntity(\n unprocessedEntity: Entity,\n state: JsonValue | undefined,\n ): Promise<EntityProcessingResult> {\n const collector = new ProcessorOutputCollector(\n this.options.logger,\n unprocessedEntity,\n );\n\n // Cache that is scoped to the entity and processor\n const cache = new ProcessorCacheManager(\n isObject(state) && isObject(state.cache) ? state.cache : {},\n );\n\n try {\n // This will be checked and mutated step by step below\n let entity: Entity = unprocessedEntity;\n\n // NOTE: At this early point, we can only rely on the envelope having to\n // be valid; full entity + kind validation happens after the (potentially\n // mutative) pre-steps. This means that the code below can't make a lot\n // of assumptions about the data despite it using the Entity type.\n try {\n validateEntityEnvelope(entity);\n } catch (e) {\n throw new InputError(\n `Entity envelope failed validation before processing`,\n e,\n );\n }\n\n // TODO: which one do we actually use for the location?\n // source-location? - maybe probably doesn't exist yet?\n const context: Context = {\n entityRef: stringifyEntityRef(entity),\n location: parseLocationRef(getEntityLocationRef(entity)),\n originLocation: parseLocationRef(getEntityOriginLocationRef(entity)),\n cache,\n collector,\n };\n\n // Run the steps\n entity = await this.runPreProcessStep(entity, context);\n entity = await this.runPolicyStep(entity);\n await this.runValidateStep(entity, context);\n if (isLocationEntity(entity)) {\n await this.runSpecialLocationStep(entity, context);\n }\n entity = await this.runPostProcessStep(entity, context);\n\n // Check that any emitted entities are permitted to originate from that\n // particular location according to the catalog rules\n const collectorResults = context.collector.results();\n for (const deferredEntity of collectorResults.deferredEntities) {\n if (\n !this.options.rulesEnforcer.isAllowed(\n deferredEntity.entity,\n context.originLocation,\n )\n ) {\n throw new NotAllowedError(\n `Entity ${stringifyEntityRef(\n deferredEntity.entity,\n )} at ${stringifyLocationRef(\n context.location,\n )}, originated at ${stringifyLocationRef(\n context.originLocation,\n )}, is not of an allowed kind for that location`,\n );\n }\n }\n\n return {\n ...collectorResults,\n completedEntity: entity,\n state: { cache: cache.collect() },\n ok: collectorResults.errors.length === 0,\n };\n } catch (error) {\n const err = toError(error);\n return {\n ok: false,\n errors: collector.results().errors.concat(err),\n };\n }\n }\n\n // Pre-process phase, used to populate entities with data that is required\n // during the main processing step\n private async runPreProcessStep(\n entity: Entity,\n context: Context,\n ): Promise<Entity> {\n return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {\n addEntityAttributes(stageSpan, entity);\n stageSpan.setAttribute('backstage.catalog.processor.stage', 'preProcess');\n let res = entity;\n\n for (const processor of this.options.processors) {\n if (processor.preProcessEntity) {\n let innerRes = res;\n res = await withActiveSpan(tracer, 'ProcessingStep', async span => {\n addEntityAttributes(span, entity);\n addProcessorAttributes(span, 'preProcessEntity', processor);\n try {\n innerRes = await processor.preProcessEntity!(\n innerRes,\n context.location,\n context.collector.forProcessor(processor),\n context.originLocation,\n context.cache.forProcessor(processor),\n );\n } catch (e) {\n throw new InputError(\n `Processor ${processor.constructor.name} threw an error while preprocessing`,\n e,\n );\n }\n return innerRes;\n });\n }\n }\n\n return res;\n });\n }\n\n /**\n * Enforce entity policies making sure that entities conform to a general schema\n */\n private async runPolicyStep(entity: Entity): Promise<Entity> {\n return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {\n addEntityAttributes(stageSpan, entity);\n stageSpan.setAttribute(\n 'backstage.catalog.processor.stage',\n 'enforcePolicy',\n );\n let policyEnforcedEntity: Entity | undefined;\n\n try {\n policyEnforcedEntity = await this.options.policy.enforce(entity);\n } catch (e) {\n throw new InputError(\n `Policy check failed for ${stringifyEntityRef(entity)}`,\n e,\n );\n }\n\n if (!policyEnforcedEntity) {\n throw new Error(\n `Policy unexpectedly returned no data for ${stringifyEntityRef(\n entity,\n )}`,\n );\n }\n\n return policyEnforcedEntity;\n });\n }\n\n /**\n * Validate the given entity\n */\n private async runValidateStep(\n entity: Entity,\n context: Context,\n ): Promise<void> {\n return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {\n addEntityAttributes(stageSpan, entity);\n stageSpan.setAttribute('backstage.catalog.processor.stage', 'validate');\n // Double check that none of the previous steps tried to change something\n // related to the entity ref, which would break downstream\n if (stringifyEntityRef(entity) !== context.entityRef) {\n throw new ConflictError(\n 'Fatal: The entity kind, namespace, or name changed during processing',\n );\n }\n\n // Validate that the end result is a valid Entity at all\n try {\n validateEntity(entity);\n } catch (e) {\n throw new ConflictError(\n `Entity envelope for ${context.entityRef} failed validation after preprocessing`,\n e,\n );\n }\n\n let valid = false;\n\n for (const processor of this.options.processors) {\n if (processor.validateEntityKind) {\n try {\n const thisValid = await withActiveSpan(\n tracer,\n 'ProcessingStep',\n async span => {\n addEntityAttributes(span, entity);\n addProcessorAttributes(span, 'validateEntityKind', processor);\n return await processor.validateEntityKind!(entity);\n },\n );\n if (thisValid) {\n valid = true;\n }\n } catch (e) {\n throw new InputError(\n `Processor ${processor.constructor.name} threw an error while validating the entity ${context.entityRef}`,\n e,\n );\n }\n }\n }\n\n if (!valid) {\n throw new InputError(\n `No processor recognized the entity ${context.entityRef} as valid, possibly caused by a foreign kind or apiVersion`,\n );\n }\n });\n }\n\n /**\n * Backwards compatible processing of location entities\n */\n private async runSpecialLocationStep(\n entity: LocationEntity,\n context: Context,\n ): Promise<void> {\n return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {\n addEntityAttributes(stageSpan, entity);\n stageSpan.setAttribute(\n 'backstage.catalog.processor.stage',\n 'readLocation',\n );\n const { type = context.location.type, presence = 'required' } =\n entity.spec;\n const targets = new Array<string>();\n if (entity.spec.target) {\n targets.push(entity.spec.target);\n }\n if (entity.spec.targets) {\n targets.push(...entity.spec.targets);\n }\n\n for (const maybeRelativeTarget of targets) {\n if (type === 'file' && maybeRelativeTarget.endsWith(path.sep)) {\n context.collector.generic()(\n processingResult.inputError(\n context.location,\n `LocationEntityProcessor cannot handle ${type} type location with target ${context.location.target} that ends with a path separator`,\n ),\n );\n continue;\n }\n const target = toAbsoluteUrl(\n this.options.integrations,\n context.location,\n type,\n maybeRelativeTarget,\n );\n\n let didRead = false;\n for (const processor of this.options.processors) {\n if (processor.readLocation) {\n try {\n const read = await withActiveSpan(\n tracer,\n 'ProcessingStep',\n async span => {\n addEntityAttributes(span, entity);\n addProcessorAttributes(span, 'readLocation', processor);\n return await processor.readLocation!(\n {\n type,\n target,\n presence,\n },\n presence === 'optional',\n context.collector.forProcessor(processor),\n this.options.parser,\n context.cache.forProcessor(processor, target),\n );\n },\n );\n if (read) {\n didRead = true;\n break;\n }\n } catch (e) {\n throw new InputError(\n `Processor ${processor.constructor.name} threw an error while reading ${type}:${target}`,\n e,\n );\n }\n }\n }\n if (!didRead) {\n throw new InputError(\n `No processor was able to handle reading of ${type}:${target}`,\n );\n }\n }\n });\n }\n\n /**\n * Main processing step of the entity\n */\n private async runPostProcessStep(\n entity: Entity,\n context: Context,\n ): Promise<Entity> {\n return await withActiveSpan(tracer, 'ProcessingStage', async stageSpan => {\n addEntityAttributes(stageSpan, entity);\n stageSpan.setAttribute(\n 'backstage.catalog.processor.stage',\n 'postProcessEntity',\n );\n let res = entity;\n\n for (const processor of this.options.processors) {\n if (processor.postProcessEntity) {\n let innerRes = res;\n res = await withActiveSpan(tracer, 'ProcessingStep', async span => {\n addEntityAttributes(span, entity);\n addProcessorAttributes(span, 'postProcessEntity', processor);\n try {\n innerRes = await processor.postProcessEntity!(\n innerRes,\n context.location,\n context.collector.forProcessor(processor),\n context.cache.forProcessor(processor),\n );\n } catch (e) {\n throw new InputError(\n `Processor ${processor.constructor.name} threw an error while postprocessing`,\n e,\n );\n }\n return innerRes;\n });\n }\n }\n\n return res;\n });\n }\n}\n"],"names":["trace","TRACER_ID","ProcessorOutputCollector","ProcessorCacheManager","isObject","validateEntityEnvelope","InputError","stringifyEntityRef","parseLocationRef","getEntityLocationRef","getEntityOriginLocationRef","isLocationEntity","NotAllowedError","stringifyLocationRef","toError","withActiveSpan","addEntityAttributes","ConflictError","validateEntity","path","processingResult","toAbsoluteUrl"],"mappings":"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"}
|
|
@@ -48,9 +48,11 @@ class ProcessorOutputCollector {
|
|
|
48
48
|
try {
|
|
49
49
|
entity = util.validateEntityEnvelope(i.entity);
|
|
50
50
|
} catch (e) {
|
|
51
|
-
errors.
|
|
52
|
-
logger.debug(
|
|
53
|
-
|
|
51
|
+
const validationError = errors.toError(e);
|
|
52
|
+
logger.debug(
|
|
53
|
+
`Envelope validation failed at ${location}, ${validationError}`
|
|
54
|
+
);
|
|
55
|
+
this.errors.push(validationError);
|
|
54
56
|
return;
|
|
55
57
|
}
|
|
56
58
|
const entityRef = catalogModel.stringifyEntityRef(entity);
|