diff --git a/.changeset/flat-deers-raise.md b/.changeset/flat-deers-raise.md new file mode 100644 index 0000000000..2595323e3a --- /dev/null +++ b/.changeset/flat-deers-raise.md @@ -0,0 +1,6 @@ +--- +'@backstage/catalog-model': minor +'@backstage/plugin-catalog-backend': minor +--- + +Add `dependencyOf` prop to catalog model for Component kind to enable building relationship graphs with both directions using `dependsOn` and `dependencyOf`. diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 57c2283586..17bdac56e9 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -527,6 +527,8 @@ spec: system: artist-engagement-portal dependsOn: - resource:default/artists-db + dependencyOf: + - component:default/artist-web-lookup providesApis: - artist-api ``` @@ -636,6 +638,17 @@ field is optional. | [`Component`](#kind-component) | Same as this entity, typically `default` | [`dependsOn`, and reverse `dependencyOf`](well-known-relations.md#dependson-and-dependencyof) | | [`Resource`](#kind-resource) | Same as this entity, typically `default` | [`dependsOn`, and reverse `dependencyOf`](well-known-relations.md#dependson-and-dependencyof) | +### `spec.dependencyOf` [optional] + +An array of [entity references](references.md#string-references) to the +components and resources that the component is a dependency of, e.g. `artist-web-lookup`. +This field is optional. + +| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type | +| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------- | +| [`Component`](#kind-component) | Same as this entity, typically `default` | [`dependencyOf`, and reverse `dependsOn`](well-known-relations.md#dependson-and-dependencyof) | +| [`Resource`](#kind-resource) | Same as this entity, typically `default` | [`dependencyOf`, and reverse `dependsOn`](well-known-relations.md#dependson-and-dependencyof) | + ## Kind: Template The following describes the following entity kind: diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index de941e2502..ec03ce41e1 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -87,6 +87,7 @@ interface ComponentEntityV1alpha1 extends Entity { providesApis?: string[]; consumesApis?: string[]; dependsOn?: string[]; + dependencyOf?: string[]; system?: string; }; } diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts index f931eab553..9a68d26cac 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts @@ -38,6 +38,7 @@ export interface ComponentEntityV1alpha1 extends Entity { providesApis?: string[]; consumesApis?: string[]; dependsOn?: string[]; + dependencyOf?: string[]; system?: string; }; } diff --git a/plugins/catalog-backend/src/modules/core/BuiltinKindsEntityProcessor.test.ts b/plugins/catalog-backend/src/modules/core/BuiltinKindsEntityProcessor.test.ts index e0368d40a6..8fefb62bfc 100644 --- a/plugins/catalog-backend/src/modules/core/BuiltinKindsEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/BuiltinKindsEntityProcessor.test.ts @@ -46,13 +46,14 @@ describe('BuiltinKindsEntityProcessor', () => { providesApis: ['b'], consumesApis: ['c'], dependsOn: ['Resource:r', 'Component:d'], + dependencyOf: ['Resource:f', 'Component:g'], system: 's', }, }; await processor.postProcessEntity(entity, location, emit); - expect(emit).toHaveBeenCalledTimes(14); + expect(emit).toHaveBeenCalledTimes(18); expect(emit).toHaveBeenCalledWith({ type: 'relation', relation: { @@ -133,6 +134,38 @@ describe('BuiltinKindsEntityProcessor', () => { target: { kind: 'Component', namespace: 'default', name: 'n' }, }, }); + expect(emit).toHaveBeenCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'n' }, + type: 'dependencyOf', + target: { kind: 'Resource', namespace: 'default', name: 'f' }, + }, + }); + expect(emit).toHaveBeenCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Resource', namespace: 'default', name: 'f' }, + type: 'dependsOn', + target: { kind: 'Component', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toHaveBeenCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'n' }, + type: 'dependencyOf', + target: { kind: 'Component', namespace: 'default', name: 'g' }, + }, + }); + expect(emit).toHaveBeenCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'g' }, + type: 'dependsOn', + target: { kind: 'Component', namespace: 'default', name: 'n' }, + }, + }); expect(emit).toHaveBeenCalledWith({ type: 'relation', relation: { @@ -190,6 +223,29 @@ describe('BuiltinKindsEntityProcessor', () => { ); }); + it('generates an error for component entities with unspecified dependencyOf entity reference kinds', async () => { + const entity: ComponentEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { name: 'n' }, + spec: { + type: 'service', + owner: 'o', + subcomponentOf: 's', + lifecycle: 'l', + providesApis: ['b'], + consumesApis: ['c'], + dependencyOf: ['y'], + system: 's', + }, + }; + await expect( + processor.postProcessEntity(entity, location, emit), + ).rejects.toThrow( + 'Entity reference "y" had missing or empty kind (e.g. did not start with "component:" or similar)', + ); + }); + it('generates relations for api entities', async () => { const entity: ApiEntity = { apiVersion: 'backstage.io/v1alpha1', diff --git a/plugins/catalog-backend/src/modules/core/BuiltinKindsEntityProcessor.ts b/plugins/catalog-backend/src/modules/core/BuiltinKindsEntityProcessor.ts index c9ea0dbdc1..fd1195a13a 100644 --- a/plugins/catalog-backend/src/modules/core/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/BuiltinKindsEntityProcessor.ts @@ -166,6 +166,12 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { RELATION_DEPENDS_ON, RELATION_DEPENDENCY_OF, ); + doEmit( + component.spec.dependencyOf, + { defaultNamespace: selfRef.namespace }, + RELATION_DEPENDENCY_OF, + RELATION_DEPENDS_ON, + ); doEmit( component.spec.system, { defaultKind: 'System', defaultNamespace: selfRef.namespace },