From eeb6f3ba930e7516cabfb01d25e6903b5c035f93 Mon Sep 17 00:00:00 2001 From: James Turley Date: Tue, 12 Jan 2021 14:22:25 +0000 Subject: [PATCH 1/4] Add subcomponentOf field to Component spec --- .../src/kinds/ComponentEntityV1alpha1.test.ts | 16 ++++++++++++++++ .../src/kinds/ComponentEntityV1alpha1.ts | 2 ++ 2 files changed, 18 insertions(+) diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts index 030c99c151..10d66ac880 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts @@ -33,6 +33,7 @@ describe('ComponentV1alpha1Validator', () => { type: 'service', lifecycle: 'production', owner: 'me', + subcomponentOf: 'monolith', providesApis: ['api-0'], consumesApis: ['api-0'], }, @@ -103,6 +104,21 @@ describe('ComponentV1alpha1Validator', () => { await expect(validator.check(entity)).rejects.toThrow(/owner/); }); + it('accepts missing subcomponentOf', async () => { + delete (entity as any).spec.subcomponentOf; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('rejects wrong subcomponentOf', async () => { + (entity as any).spec.subcomponentOf = 7; + await expect(validator.check(entity)).rejects.toThrow(/subcomponentOf/); + }); + + it('rejects empty subcomponentOf', async () => { + (entity as any).spec.subcomponentOf = ''; + await expect(validator.check(entity)).rejects.toThrow(/subcomponentOf/); + }); + it('accepts missing providesApis', async () => { delete (entity as any).spec.providesApis; await expect(validator.check(entity)).resolves.toBe(true); diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts index e511dcb24a..97519ad403 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts @@ -29,6 +29,7 @@ const schema = yup.object>({ type: yup.string().required().min(1), lifecycle: yup.string().required().min(1), owner: yup.string().required().min(1), + subcomponentOf: yup.string().notRequired().min(1), providesApis: yup.array(yup.string().required()).notRequired(), consumesApis: yup.array(yup.string().required()).notRequired(), }) @@ -42,6 +43,7 @@ export interface ComponentEntityV1alpha1 extends Entity { type: string; lifecycle: string; owner: string; + subcomponentOf?: string; providesApis?: string[]; consumesApis?: string[]; }; From 868f81399213ae4d68adaf6e3c6097b1e38dd0a7 Mon Sep 17 00:00:00 2001 From: James Turley Date: Tue, 12 Jan 2021 14:48:23 +0000 Subject: [PATCH 2/4] Emit relations for subcomponents --- packages/catalog-model/src/kinds/relations.ts | 7 +++++++ .../BuiltinKindsEntityProcessor.test.ts | 19 ++++++++++++++++++- .../processors/BuiltinKindsEntityProcessor.ts | 8 ++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/catalog-model/src/kinds/relations.ts b/packages/catalog-model/src/kinds/relations.ts index 3d5d629b9e..78bbc61df2 100644 --- a/packages/catalog-model/src/kinds/relations.ts +++ b/packages/catalog-model/src/kinds/relations.ts @@ -55,3 +55,10 @@ export const RELATION_CHILD_OF = 'childOf'; */ export const RELATION_MEMBER_OF = 'memberOf'; export const RELATION_HAS_MEMBER = 'hasMember'; + +/** + * A part/whole relation, typically for components in a system and systems + * in a domain. + */ +export const RELATION_PART_OF = 'partOf'; +export const RELATION_HAS_PART = 'hasPart'; diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts index 94a08c5e73..1d2562c25b 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts @@ -38,6 +38,7 @@ describe('BuiltinKindsEntityProcessor', () => { spec: { type: 'service', owner: 'o', + subcomponentOf: 's', lifecycle: 'l', providesApis: ['b'], consumesApis: ['c'], @@ -46,7 +47,7 @@ describe('BuiltinKindsEntityProcessor', () => { await processor.postProcessEntity(entity, location, emit); - expect(emit).toBeCalledTimes(6); + expect(emit).toBeCalledTimes(8); expect(emit).toBeCalledWith({ type: 'relation', relation: { @@ -95,6 +96,22 @@ describe('BuiltinKindsEntityProcessor', () => { target: { kind: 'API', namespace: 'default', name: 'c' }, }, }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 's' }, + type: 'hasPart', + target: { kind: 'Component', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'n' }, + type: 'partOf', + target: { kind: 'Component', namespace: 'default', name: 's' }, + }, + }); }); it('generates relations for api entities', async () => { diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts index 6cbec5d655..67e89ac52c 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts @@ -32,6 +32,8 @@ import { RELATION_CONSUMES_API, RELATION_HAS_MEMBER, RELATION_MEMBER_OF, + RELATION_HAS_PART, + RELATION_PART_OF, RELATION_OWNED_BY, RELATION_OWNER_OF, RELATION_PARENT_OF, @@ -115,6 +117,12 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { RELATION_OWNED_BY, RELATION_OWNER_OF, ); + doEmit( + component.spec.subcomponentOf, + { defaultKind: 'Component', defaultNamespace: selfRef.namespace }, + RELATION_PART_OF, + RELATION_HAS_PART, + ); doEmit( component.spec.providesApis, { defaultKind: 'API', defaultNamespace: selfRef.namespace }, From 9b9a56129874096bf274ceff348673da0d1ff747 Mon Sep 17 00:00:00 2001 From: James Turley Date: Tue, 12 Jan 2021 14:54:39 +0000 Subject: [PATCH 3/4] Document subcomponentOf field --- docs/features/software-catalog/descriptor-format.md | 9 +++++++++ docs/features/software-catalog/well-known-relations.md | 10 ++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 725f358b92..12d13069c3 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -454,6 +454,15 @@ 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.subcomponentOf` [optional] + +An [entity reference](#string-references) to another component of which the +component is a part, e.g. `spotify-ios-app`. 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` | [`partOf`, and reverse `hasPart`](well-known-relations.md#partof-and-haspart) | + ### `spec.providesApis` [optional] An array of [entity references](#string-references) to the APIs that are diff --git a/docs/features/software-catalog/well-known-relations.md b/docs/features/software-catalog/well-known-relations.md index af2e7367f2..6fb7ae2fea 100644 --- a/docs/features/software-catalog/well-known-relations.md +++ b/docs/features/software-catalog/well-known-relations.md @@ -92,13 +92,15 @@ This relation is commonly based on `spec.memberOf`. ### `partOf` and `hasPart` -A relation with a [Domain](descriptor-format.md#kind-domain) or -[System](descriptor-format.md#kind-system) entity, typically from a +A relation with a [Domain](descriptor-format.md#kind-domain), +[System](descriptor-format.md#kind-system) or +[Component](descriptor-format.md#kind-component) entity, typically from a [Component](descriptor-format.md#kind-component), [API](descriptor-format.md#kind-api), or [System](descriptor-format.md#kind-system). -These relations express that a component, API or resource belongs to a system, -or that a system is grouped under a domain. +These relations express that a component belongs to a larger component; a +component, API or resource belongs to a system; or that a system is grouped +under a domain. This relation is commonly based on `spec.system` or `spec.domain`. From 147fadcb9d529f2da1cc602da6934dfbaa915ae0 Mon Sep 17 00:00:00 2001 From: James Turley Date: Tue, 12 Jan 2021 16:15:16 +0000 Subject: [PATCH 4/4] Changeset for catalog packages --- .changeset/twelve-gorillas-give.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/twelve-gorillas-give.md diff --git a/.changeset/twelve-gorillas-give.md b/.changeset/twelve-gorillas-give.md new file mode 100644 index 0000000000..ed13fc773c --- /dev/null +++ b/.changeset/twelve-gorillas-give.md @@ -0,0 +1,6 @@ +--- +'@backstage/catalog-model': patch +'@backstage/plugin-catalog-backend': patch +--- + +Add subcomponentOf to Component kind to represent subsystems of larger components.