From add62a4552b3f96665a71dc38c31ce2397ea98c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 24 May 2021 09:46:01 +0200 Subject: [PATCH] Foundation for standard status values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/great-ducks-bow.md | 7 +++ packages/catalog-client/api-report.md | 18 ++++++ .../catalog-client/src/CatalogClient.test.ts | 3 +- packages/catalog-client/src/CatalogClient.ts | 4 +- packages/catalog-client/src/index.ts | 8 +-- .../src/{types.ts => types/api.ts} | 9 +-- .../catalog-client/src/types/discovery.ts | 22 +++++++ packages/catalog-client/src/types/index.ts | 28 +++++++++ packages/catalog-client/src/types/status.ts | 63 +++++++++++++++++++ packages/catalog-model/api-report.md | 9 +++ packages/catalog-model/src/entity/index.ts | 6 +- packages/catalog-model/src/entity/status.ts | 32 ++++++++++ plugins/catalog-backend/package.json | 1 + .../next/DefaultCatalogProcessingEngine.ts | 3 +- .../catalog-backend/src/next/Stitcher.test.ts | 5 +- plugins/catalog-backend/src/next/Stitcher.ts | 24 ++++--- 16 files changed, 210 insertions(+), 32 deletions(-) create mode 100644 .changeset/great-ducks-bow.md rename packages/catalog-client/src/{types.ts => types/api.ts} (91%) create mode 100644 packages/catalog-client/src/types/discovery.ts create mode 100644 packages/catalog-client/src/types/index.ts create mode 100644 packages/catalog-client/src/types/status.ts create mode 100644 packages/catalog-model/src/entity/status.ts diff --git a/.changeset/great-ducks-bow.md b/.changeset/great-ducks-bow.md new file mode 100644 index 0000000000..ff5d3a3458 --- /dev/null +++ b/.changeset/great-ducks-bow.md @@ -0,0 +1,7 @@ +--- +'@backstage/catalog-client': patch +'@backstage/catalog-model': patch +'@backstage/plugin-catalog-backend': patch +--- + +Foundation for standard entity status values diff --git a/packages/catalog-client/api-report.md b/packages/catalog-client/api-report.md index 4b489ff2e4..003e585671 100644 --- a/packages/catalog-client/api-report.md +++ b/packages/catalog-client/api-report.md @@ -7,6 +7,9 @@ import { Entity } from '@backstage/catalog-model'; import { EntityName } from '@backstage/catalog-model'; import { Location as Location_2 } from '@backstage/catalog-model'; +import { SerializedError } from '@backstage/errors'; +import { UNSTABLE_EntityStatusLevel } from '@backstage/catalog-model'; +import { UNSTABLE_EntityStatusValue } from '@backstage/catalog-model'; // @public (undocumented) export type AddLocationRequest = { @@ -76,6 +79,21 @@ export type CatalogListResponse = { items: T[]; }; +// @public +export const ENTITY_STATUS_CATALOG_PROCESSING_KEY = "backstage.io/catalog-processing"; + +// @public +export type UNSTABLE_CatalogProcessingStatus = UNSTABLE_EntityStatusValue & { + items?: UNSTABLE_CatalogProcessingStatusItem[]; +}; + +// @public (undocumented) +export type UNSTABLE_CatalogProcessingStatusItem = { + status: UNSTABLE_EntityStatusLevel; + message?: string; + error?: SerializedError; +}; + // (No @packageDocumentation comment for this package) diff --git a/packages/catalog-client/src/CatalogClient.test.ts b/packages/catalog-client/src/CatalogClient.test.ts index 359e3a2c60..98b378f5b0 100644 --- a/packages/catalog-client/src/CatalogClient.test.ts +++ b/packages/catalog-client/src/CatalogClient.test.ts @@ -18,7 +18,8 @@ import { Entity } from '@backstage/catalog-model'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { CatalogClient } from './CatalogClient'; -import { CatalogListResponse, DiscoveryApi } from './types'; +import { CatalogListResponse } from './types/api'; +import { DiscoveryApi } from './types/discovery'; const server = setupServer(); const token = 'fake-token'; diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index 063db5a6a3..7c9b313ce7 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -31,8 +31,8 @@ import { CatalogEntitiesRequest, CatalogListResponse, CatalogRequestOptions, - DiscoveryApi, -} from './types'; +} from './types/api'; +import { DiscoveryApi } from './types/discovery'; export class CatalogClient implements CatalogApi { private readonly discoveryApi: DiscoveryApi; diff --git a/packages/catalog-client/src/index.ts b/packages/catalog-client/src/index.ts index c5a626e25b..59c652f9fd 100644 --- a/packages/catalog-client/src/index.ts +++ b/packages/catalog-client/src/index.ts @@ -15,10 +15,4 @@ */ export { CatalogClient } from './CatalogClient'; -export type { - AddLocationRequest, - AddLocationResponse, - CatalogApi, - CatalogEntitiesRequest, - CatalogListResponse, -} from './types'; +export * from './types'; diff --git a/packages/catalog-client/src/types.ts b/packages/catalog-client/src/types/api.ts similarity index 91% rename from packages/catalog-client/src/types.ts rename to packages/catalog-client/src/types/api.ts index ef907eafa9..f9ba2e9d8d 100644 --- a/packages/catalog-client/src/types.ts +++ b/packages/catalog-client/src/types/api.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 Spotify AB + * Copyright 2021 Spotify AB * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,10 +81,3 @@ export type AddLocationResponse = { location: Location; entities: Entity[]; }; - -/** - * This is a copy of the core DiscoveryApi, to avoid importing core. - */ -export type DiscoveryApi = { - getBaseUrl(pluginId: string): Promise; -}; diff --git a/packages/catalog-client/src/types/discovery.ts b/packages/catalog-client/src/types/discovery.ts new file mode 100644 index 0000000000..90eb748b2f --- /dev/null +++ b/packages/catalog-client/src/types/discovery.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This is a copy of the core DiscoveryApi, to avoid importing core. + */ +export type DiscoveryApi = { + getBaseUrl(pluginId: string): Promise; +}; diff --git a/packages/catalog-client/src/types/index.ts b/packages/catalog-client/src/types/index.ts new file mode 100644 index 0000000000..16850a0fc0 --- /dev/null +++ b/packages/catalog-client/src/types/index.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type { + AddLocationRequest, + AddLocationResponse, + CatalogApi, + CatalogEntitiesRequest, + CatalogListResponse, +} from './api'; +export { ENTITY_STATUS_CATALOG_PROCESSING_KEY } from './status'; +export type { + UNSTABLE_CatalogProcessingStatus, + UNSTABLE_CatalogProcessingStatusItem, +} from './status'; diff --git a/packages/catalog-client/src/types/status.ts b/packages/catalog-client/src/types/status.ts new file mode 100644 index 0000000000..6c39a83ea7 --- /dev/null +++ b/packages/catalog-client/src/types/status.ts @@ -0,0 +1,63 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + UNSTABLE_EntityStatusLevel, + UNSTABLE_EntityStatusValue, +} from '@backstage/catalog-model'; +import { SerializedError } from '@backstage/errors'; + +/* + * This is the entity status field that's emitted by the catalog processing + * engine, to inform about the status of an entity. + * + * Example: + * + * "status": { + * "backstage.io/catalog-processing": { + * "status": "error", + * "items": [ + * { + * "status": "error", + * "error": { + * "name": "InputError", + * "message": "Syntax error: ..." + * } + * } + * ] + * } + * } + */ + +/** + * The entity `status` key for the status of the processing engine in regards + * to entity. + */ +export const ENTITY_STATUS_CATALOG_PROCESSING_KEY = + 'backstage.io/catalog-processing'; + +/** + * The status value for the `backstage.io/catalog-processing` key. + */ +export type UNSTABLE_CatalogProcessingStatus = UNSTABLE_EntityStatusValue & { + items?: UNSTABLE_CatalogProcessingStatusItem[]; +}; + +export type UNSTABLE_CatalogProcessingStatusItem = { + status: UNSTABLE_EntityStatusLevel; + message?: string; + error?: SerializedError; +}; diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index 890a45b6e7..c9118ce52c 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -528,6 +528,15 @@ export interface TemplateEntityV1beta2 extends Entity { // @public (undocumented) export const templateEntityV1beta2Validator: KindValidator; +// @public +export type UNSTABLE_EntityStatusLevel = 'ok' | 'info' | 'warning' | 'error'; + +// @public +export type UNSTABLE_EntityStatusValue = { + status: UNSTABLE_EntityStatusLevel; + message?: string; +}; + // @public (undocumented) interface UserEntityV1alpha1 extends Entity { // (undocumented) diff --git a/packages/catalog-model/src/entity/index.ts b/packages/catalog-model/src/entity/index.ts index 572df63557..330856125e 100644 --- a/packages/catalog-model/src/entity/index.ts +++ b/packages/catalog-model/src/entity/index.ts @@ -15,10 +15,10 @@ */ export { + EDIT_URL_ANNOTATION, ENTITY_DEFAULT_NAMESPACE, ENTITY_META_GENERATED_FIELDS, VIEW_URL_ANNOTATION, - EDIT_URL_ANNOTATION, } from './constants'; export type { Entity, @@ -36,6 +36,10 @@ export { serializeEntityRef, stringifyEntityRef, } from './ref'; +export type { + UNSTABLE_EntityStatusLevel, + UNSTABLE_EntityStatusValue, +} from './status'; export { entityHasChanges, generateEntityEtag, diff --git a/packages/catalog-model/src/entity/status.ts b/packages/catalog-model/src/entity/status.ts new file mode 100644 index 0000000000..a819bea8f6 --- /dev/null +++ b/packages/catalog-model/src/entity/status.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Each entity status entry has a level, describing its severity. + */ +export type UNSTABLE_EntityStatusLevel = + | 'ok' // Everything is OK + | 'info' // Only informative data + | 'warning' // Warnings were found + | 'error'; // Errors were found + +/** + * Reserved root fields in all `status` object values. + */ +export type UNSTABLE_EntityStatusValue = { + status: UNSTABLE_EntityStatusLevel; + message?: string; +}; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index f55ca2c13f..2f0164ace5 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -31,6 +31,7 @@ "dependencies": { "@azure/msal-node": "^1.0.0-beta.3", "@backstage/backend-common": "^0.8.1", + "@backstage/catalog-client": "^0.3.11", "@backstage/catalog-model": "^0.7.10", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", diff --git a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts index 55cb8e2029..657c331a25 100644 --- a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts @@ -15,6 +15,7 @@ */ import { stringifyEntityRef } from '@backstage/catalog-model'; +import { serializeError } from '@backstage/errors'; import { Logger } from 'winston'; import { ProcessingDatabase } from './database/types'; import { Stitcher } from './Stitcher'; @@ -128,7 +129,7 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { id, processedEntity: result.completedEntity, state: result.state, - errors: JSON.stringify(result.errors), + errors: JSON.stringify(result.errors.map(e => serializeError(e))), relations: result.relations, deferredEntities: result.deferredEntities, }); diff --git a/plugins/catalog-backend/src/next/Stitcher.test.ts b/plugins/catalog-backend/src/next/Stitcher.test.ts index 644284f3e4..75286743c3 100644 --- a/plugins/catalog-backend/src/next/Stitcher.test.ts +++ b/plugins/catalog-backend/src/next/Stitcher.test.ts @@ -16,6 +16,7 @@ import { getVoidLogger } from '@backstage/backend-common'; import { Knex } from 'knex'; +import { ENTITY_STATUS_CATALOG_PROCESSING_KEY } from '@backstage/catalog-client'; import { DatabaseManager } from './database/DatabaseManager'; import { DbRefreshStateReferencesRow, @@ -92,7 +93,7 @@ describe('Stitcher', () => { }, ], status: { - 'backstage.io/catalog-processing': {}, + [ENTITY_STATUS_CATALOG_PROCESSING_KEY]: { status: 'ok' }, }, apiVersion: 'a', kind: 'k', @@ -175,7 +176,7 @@ describe('Stitcher', () => { }, ]), status: { - 'backstage.io/catalog-processing': {}, + [ENTITY_STATUS_CATALOG_PROCESSING_KEY]: { status: 'ok' }, }, apiVersion: 'a', kind: 'k', diff --git a/plugins/catalog-backend/src/next/Stitcher.ts b/plugins/catalog-backend/src/next/Stitcher.ts index d7d4325ad0..36ead7d817 100644 --- a/plugins/catalog-backend/src/next/Stitcher.ts +++ b/plugins/catalog-backend/src/next/Stitcher.ts @@ -14,9 +14,12 @@ * limitations under the License. */ +import { + ENTITY_STATUS_CATALOG_PROCESSING_KEY, + UNSTABLE_CatalogProcessingStatusItem, +} from '@backstage/catalog-client'; import { Entity, parseEntityRef } from '@backstage/catalog-model'; -import { JsonObject } from '@backstage/config'; -import { ConflictError } from '@backstage/errors'; +import { ConflictError, SerializedError } from '@backstage/errors'; import { createHash } from 'crypto'; import stableStringify from 'fast-json-stable-stringify'; import { Knex } from 'knex'; @@ -36,10 +39,6 @@ export type DbFinalEntitiesRow = { final_entity: string; }; -type ProcessingStatus = { - errors?: JsonObject[]; -}; - function generateStableHash(entity: Entity) { return createHash('sha1') .update(stableStringify({ ...entity })) @@ -139,7 +138,7 @@ export class Stitcher { // it const entity = JSON.parse(processedEntity) as Entity; const isOrphan = Number(incomingReferenceCount) === 0; - const processingStatus: ProcessingStatus = {}; + let statusItems: UNSTABLE_CatalogProcessingStatusItem[] = []; if (isOrphan) { this.logger.debug(`${entityRef} is an orphan`); @@ -149,9 +148,12 @@ export class Stitcher { }; } if (errors) { - const parsedErrors = JSON.parse(errors); + const parsedErrors = JSON.parse(errors) as SerializedError[]; if (Array.isArray(parsedErrors) && parsedErrors.length) { - processingStatus.errors = parsedErrors; + statusItems = parsedErrors.map(e => ({ + status: 'error', + error: e, + })); } } @@ -165,7 +167,9 @@ export class Stitcher { })); entity.status = { ...entity.status, - 'backstage.io/catalog-processing': processingStatus, + [ENTITY_STATUS_CATALOG_PROCESSING_KEY]: statusItems.length + ? { status: 'error', items: statusItems } + : { status: 'ok' }, }; // If the output entity was actually not changed, just abort