diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 668b139d6a..16829b6716 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -521,12 +521,14 @@ consumed by the component, e.g. `artist-api`. This field is optional. ### `spec.dependsOn` [optional] -An array of [entity references](#string-references) to the Resources that the -component depends on, e.g. `artists-db`. This field is optional. +An array of [entity references](#string-references) to the components and +resources that the component depends on, e.g. `artists-db`. This field is +optional. | [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type | | --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------- | -| [`Resource`](#kind-resource) (default) | Same as this entity, typically `default` | [`dependsOn`, and reverse `dependencyOf`](well-known-relations.md#dependson-and-dependencyof) | +| [`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) | ## Kind: Template @@ -1010,14 +1012,16 @@ belongs to, e.g. `artist-engagement-portal`. This field is optional. | --------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- | | [`System`](#kind-system) (default) | Same as this entity, typically `default` | [`partOf`, and reverse `hasPart`](well-known-relations.md#partof-and-haspart) | -### `spec.dependencyOf` [optional] +### `spec.dependsOn` [optional] -An array of [entity references](#string-references) to the Components that the -resource is a dependency of, e.g. `artist-lookup`. This field is optional. +An array of [entity references](#string-references) to the components and +resources that the resource depends on, e.g. `artist-lookup`. This field is +optional. -| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type | -| ---------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------- | -| [`Component`](#kind-component) (default) | Same as this entity, typically `default` | [`dependencyOf`, and reverse `dependsOn`](well-known-relations.md#dependson-and-dependencyof) | +| [`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` | [`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) | ## Kind: System diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts index 19bc168223..358e7b6526 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts @@ -36,7 +36,7 @@ describe('ComponentV1alpha1Validator', () => { subcomponentOf: 'monolith', providesApis: ['api-0'], consumesApis: ['api-0'], - dependsOn: ['resource-0'], + dependsOn: ['resource:resource-0', 'component:component-0'], system: 'system', }, }; diff --git a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts index 076288628a..953ec5889a 100644 --- a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.test.ts @@ -32,7 +32,7 @@ describe('ResourceV1alpha1Validator', () => { spec: { type: 'database', owner: 'me', - dependencyOf: ['component-0'], + dependsOn: ['component:component-0', 'resource:resource-0'], system: 'system', }, }; @@ -87,23 +87,23 @@ describe('ResourceV1alpha1Validator', () => { await expect(validator.check(entity)).rejects.toThrow(/owner/); }); - it('accepts missing dependencyOf', async () => { - delete (entity as any).spec.dependencyOf; + it('accepts missing dependsOn', async () => { + delete (entity as any).spec.dependsOn; await expect(validator.check(entity)).resolves.toBe(true); }); - it('rejects empty dependencyOf', async () => { - (entity as any).spec.dependencyOf = ['']; - await expect(validator.check(entity)).rejects.toThrow(/dependencyOf/); + it('rejects empty dependsOn', async () => { + (entity as any).spec.dependsOn = ['']; + await expect(validator.check(entity)).rejects.toThrow(/dependsOn/); }); - it('rejects undefined dependencyOf', async () => { - (entity as any).spec.dependencyOf = [undefined]; - await expect(validator.check(entity)).rejects.toThrow(/dependencyOf/); + it('rejects undefined dependsOn', async () => { + (entity as any).spec.dependsOn = [undefined]; + await expect(validator.check(entity)).rejects.toThrow(/dependsOn/); }); - it('accepts no dependencyOf', async () => { - (entity as any).spec.dependencyOf = []; + it('accepts no dependsOn', async () => { + (entity as any).spec.dependsOn = []; await expect(validator.check(entity)).resolves.toBe(true); }); diff --git a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts index 0b3662b91f..fd71500f40 100644 --- a/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ResourceEntityV1alpha1.ts @@ -30,7 +30,7 @@ export interface ResourceEntityV1alpha1 extends Entity { spec: { type: string; owner: string; - dependencyOf?: string[]; + dependsOn?: string[]; system?: string; }; } diff --git a/packages/catalog-model/src/schema/kinds/Resource.v1alpha1.schema.json b/packages/catalog-model/src/schema/kinds/Resource.v1alpha1.schema.json index dbe9c969f3..2dcf6f4eb2 100644 --- a/packages/catalog-model/src/schema/kinds/Resource.v1alpha1.schema.json +++ b/packages/catalog-model/src/schema/kinds/Resource.v1alpha1.schema.json @@ -47,7 +47,7 @@ "examples": ["artist-relations-team", "user:john.johnson"], "minLength": 1 }, - "dependencyOf": { + "dependsOn": { "type": "array", "description": "An array of entity references to the components that the resource is a dependency of.", "items": { diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts index 2d61191aa8..ad446c6a7a 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts @@ -45,14 +45,14 @@ describe('BuiltinKindsEntityProcessor', () => { lifecycle: 'l', providesApis: ['b'], consumesApis: ['c'], - dependsOn: ['r'], + dependsOn: ['Resource:r', 'Component:d'], system: 's', }, }; await processor.postProcessEntity(entity, location, emit); - expect(emit).toBeCalledTimes(12); + expect(emit).toBeCalledTimes(14); expect(emit).toBeCalledWith({ type: 'relation', relation: { @@ -101,6 +101,14 @@ describe('BuiltinKindsEntityProcessor', () => { target: { kind: 'API', namespace: 'default', name: 'c' }, }, }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'n' }, + type: 'dependsOn', + target: { kind: 'Resource', namespace: 'default', name: 'r' }, + }, + }); expect(emit).toBeCalledWith({ type: 'relation', relation: { @@ -114,7 +122,15 @@ describe('BuiltinKindsEntityProcessor', () => { relation: { source: { kind: 'Component', namespace: 'default', name: 'n' }, type: 'dependsOn', - target: { kind: 'Resource', namespace: 'default', name: 'r' }, + target: { kind: 'Component', namespace: 'default', name: 'd' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'd' }, + type: 'dependencyOf', + target: { kind: 'Component', namespace: 'default', name: 'n' }, }, }); expect(emit).toBeCalledWith({ @@ -151,6 +167,29 @@ describe('BuiltinKindsEntityProcessor', () => { }); }); + it('generates an error for component entities with unspecified dependsOn 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'], + dependsOn: ['r'], + system: 's', + }, + }; + await expect( + processor.postProcessEntity(entity, location, emit), + ).rejects.toThrowError( + 'Entity reference kind is undefined and has no default', + ); + }); + it('generates relations for api entities', async () => { const entity: ApiEntity = { apiVersion: 'backstage.io/v1alpha1', @@ -210,14 +249,14 @@ describe('BuiltinKindsEntityProcessor', () => { spec: { type: 'database', owner: 'o', - dependencyOf: ['c'], + dependsOn: ['Component:c', 'Resource:r'], system: 's', }, }; await processor.postProcessEntity(entity, location, emit); - expect(emit).toBeCalledTimes(6); + expect(emit).toBeCalledTimes(8); expect(emit).toBeCalledWith({ type: 'relation', relation: { @@ -234,11 +273,12 @@ describe('BuiltinKindsEntityProcessor', () => { target: { kind: 'Group', namespace: 'default', name: 'o' }, }, }); + expect(emit).toBeCalledWith({ type: 'relation', relation: { source: { kind: 'Resource', namespace: 'default', name: 'n' }, - type: 'dependencyOf', + type: 'dependsOn', target: { kind: 'Component', namespace: 'default', name: 'c' }, }, }); @@ -246,10 +286,28 @@ describe('BuiltinKindsEntityProcessor', () => { type: 'relation', relation: { source: { kind: 'Component', namespace: 'default', name: 'c' }, - type: 'dependsOn', + type: 'dependencyOf', target: { kind: 'Resource', namespace: 'default', name: 'n' }, }, }); + + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Resource', namespace: 'default', name: 'n' }, + type: 'dependsOn', + target: { kind: 'Resource', namespace: 'default', name: 'r' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Resource', namespace: 'default', name: 'r' }, + type: 'dependencyOf', + target: { kind: 'Resource', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ type: 'relation', relation: { @@ -268,6 +326,25 @@ describe('BuiltinKindsEntityProcessor', () => { }); }); + it('generates an error for resource entities with unspecified dependsOn entity reference kinds', async () => { + const entity: ResourceEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Resource', + metadata: { name: 'n' }, + spec: { + type: 'database', + owner: 'o', + dependsOn: ['r'], + system: 's', + }, + }; + await expect( + processor.postProcessEntity(entity, location, emit), + ).rejects.toThrowError( + 'Entity reference kind is undefined and has no default', + ); + }); + it('generates relations for system entities', async () => { const entity: SystemEntity = { apiVersion: 'backstage.io/v1alpha1', diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts index d1de992a81..27313ba014 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts @@ -92,7 +92,7 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { function doEmit( targets: string | string[] | undefined, - context: { defaultKind: string; defaultNamespace: string }, + context: { defaultKind?: string; defaultNamespace: string }, outgoingRelation: string, incomingRelation: string, ): void { @@ -101,16 +101,29 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { } for (const target of [targets].flat()) { const targetRef = parseEntityRef(target, context); + if (targetRef.kind === undefined) { + throw new Error( + 'Entity reference kind is undefined and has no default', + ); + } emit( result.relation({ source: selfRef, type: outgoingRelation, - target: targetRef, + target: { + kind: targetRef.kind, + namespace: targetRef.namespace, + name: targetRef.name, + }, }), ); emit( result.relation({ - source: targetRef, + source: { + kind: targetRef.kind, + namespace: targetRef.namespace, + name: targetRef.name, + }, type: incomingRelation, target: selfRef, }), @@ -150,7 +163,7 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { ); doEmit( component.spec.dependsOn, - { defaultKind: 'Resource', defaultNamespace: selfRef.namespace }, + { defaultNamespace: selfRef.namespace }, RELATION_DEPENDS_ON, RELATION_DEPENDENCY_OF, ); @@ -195,10 +208,10 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { RELATION_OWNER_OF, ); doEmit( - resource.spec.dependencyOf, - { defaultKind: 'Component', defaultNamespace: selfRef.namespace }, - RELATION_DEPENDENCY_OF, + resource.spec.dependsOn, + { defaultNamespace: selfRef.namespace }, RELATION_DEPENDS_ON, + RELATION_DEPENDENCY_OF, ); doEmit( resource.spec.system,