From fb19e0241dc831ba238457df10d8f89234cc9c49 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 16:01:34 +0100 Subject: [PATCH] Add providesApis and consumesApis to component spec --- .../software-catalog/descriptor-format.md | 21 +++++++++- docs/features/software-catalog/references.md | 4 +- .../src/kinds/ComponentEntityV1alpha1.test.ts | 42 +++++++++++++++++++ .../src/kinds/ComponentEntityV1alpha1.ts | 4 ++ plugins/api-docs/README.md | 2 +- .../BuiltinKindsEntityProcessor.test.ts | 36 +++++++++++++++- .../processors/BuiltinKindsEntityProcessor.ts | 14 +++++++ 7 files changed, 118 insertions(+), 5 deletions(-) diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index f1d426f407..c86be1be83 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -381,7 +381,7 @@ spec: type: website lifecycle: production owner: artist-relations@example.com - implementsApis: + providesApis: - artist-api ``` @@ -451,6 +451,25 @@ is optional. The software catalog expects a list of one or more strings that references the names of other entities of the `kind` `API`. +This field has the same behavior as `spec.providesApis` and will be deprecated +in the future. + +### `spec.providesApis` [optional] + +Links APIs that are provided by the component, e.g. `artist-api`. This field is +optional. + +The software catalog expects a list of one or more strings that references the +names of other entities of the `kind` `API`. + +### `spec.consumesApis` [optional] + +Links APIs that are consumed by the component, e.g. `artist-api`. This field is +optional. + +The software catalog expects a list of one or more strings that references the +names of other entities of the `kind` `API`. + ## Kind: Template Describes the following entity kind: diff --git a/docs/features/software-catalog/references.md b/docs/features/software-catalog/references.md index e438ddc977..d347035950 100644 --- a/docs/features/software-catalog/references.md +++ b/docs/features/software-catalog/references.md @@ -51,7 +51,7 @@ spec: type: service lifecycle: experimental owner: group:pet-managers - implementsApis: + providesApis: - petstore - internal/streetlights - hello-world @@ -66,7 +66,7 @@ catalog that is of kind `Group`, namespace `default` (which, actually, also can be left out in its own yaml file because that's the default value there too), and name `pet-managers`. -The entries in `implementsApis` are also references. In this case, none of them +The entries in `providesApis` are also references. In this case, none of them needs to specify a kind since we know from the context that that's the only kind that's supported here. The second entry specifies a namespace but the other ones don't, and in this context, the default is to refer to the same namespace as the diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts index 64f9d05978..8eec63daac 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts @@ -34,6 +34,8 @@ describe('ComponentV1alpha1Validator', () => { lifecycle: 'production', owner: 'me', implementsApis: ['api-0'], + providesApis: ['api-0'], + consumesApis: ['api-0'], }, }; }); @@ -121,4 +123,44 @@ describe('ComponentV1alpha1Validator', () => { (entity as any).spec.implementsApis = []; await expect(validator.check(entity)).resolves.toBe(true); }); + + it('accepts missing providesApis', async () => { + delete (entity as any).spec.providesApis; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('rejects empty providesApis', async () => { + (entity as any).spec.providesApis = ['']; + await expect(validator.check(entity)).rejects.toThrow(/providesApis/); + }); + + it('rejects undefined providesApis', async () => { + (entity as any).spec.providesApis = [undefined]; + await expect(validator.check(entity)).rejects.toThrow(/providesApis/); + }); + + it('accepts no providesApis', async () => { + (entity as any).spec.providesApis = []; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('accepts missing consumesApis', async () => { + delete (entity as any).spec.consumesApis; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('rejects empty consumesApis', async () => { + (entity as any).spec.consumesApis = ['']; + await expect(validator.check(entity)).rejects.toThrow(/consumesApis/); + }); + + it('rejects undefined consumesApis', async () => { + (entity as any).spec.consumesApis = [undefined]; + await expect(validator.check(entity)).rejects.toThrow(/consumesApis/); + }); + + it('accepts no consumesApis', async () => { + (entity as any).spec.consumesApis = []; + 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 0e1e369e44..97c9140e61 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts @@ -30,6 +30,8 @@ const schema = yup.object>({ lifecycle: yup.string().required().min(1), owner: yup.string().required().min(1), implementsApis: yup.array(yup.string().required()).notRequired(), + providesApis: yup.array(yup.string().required()).notRequired(), + consumesApis: yup.array(yup.string().required()).notRequired(), kubernetes: yup .object({ selector: yup @@ -51,6 +53,8 @@ export interface ComponentEntityV1alpha1 extends Entity { lifecycle: string; owner: string; implementsApis?: string[]; + providesApis?: string[]; + consumesApis?: string[]; kubernetes?: { selector: { matchLabels: { diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md index ecc0e5a560..f5b9a79a85 100644 --- a/plugins/api-docs/README.md +++ b/plugins/api-docs/README.md @@ -21,7 +21,7 @@ Right now, the following API formats are supported: Other formats are displayed as plain text, but this can easily be extended. To fill the catalog with APIs, [provide entities of kind API](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api). -To link that an component implements an API, see [`implementsApis` property on components](https://backstage.io/docs/features/software-catalog/descriptor-format#specimplementsapis-optional). +To link that an component provides or consumes an API, see [`providesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specprovidesapis-optional) and [`consumesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specconsumesapis-optional) property on components. ## Links diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts index 94d35cc109..e456dfd7a2 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts @@ -68,12 +68,14 @@ describe('BuiltinKindsEntityProcessor', () => { owner: 'o', lifecycle: 'l', implementsApis: ['a'], + providesApis: ['b'], + consumesApis: ['c'], }, }; await processor.postProcessEntity(entity, location, emit); - expect(emit).toBeCalledTimes(4); + expect(emit).toBeCalledTimes(8); expect(emit).toBeCalledWith({ type: 'relation', relation: { @@ -106,6 +108,38 @@ describe('BuiltinKindsEntityProcessor', () => { target: { kind: 'API', namespace: 'default', name: 'a' }, }, }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'API', namespace: 'default', name: 'b' }, + type: 'apiProvidedBy', + target: { kind: 'Component', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'n' }, + type: 'providesApi', + target: { kind: 'API', namespace: 'default', name: 'b' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'API', namespace: 'default', name: 'c' }, + type: 'apiConsumedBy', + target: { kind: 'Component', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'n' }, + type: 'consumesApi', + target: { kind: 'API', namespace: 'default', name: 'c' }, + }, + }); }); 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 a4e62e5b8c..62b496dd65 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts @@ -26,8 +26,10 @@ import { locationEntityV1alpha1Validator, LocationSpec, parseEntityRef, + RELATION_API_CONSUMED_BY, RELATION_API_PROVIDED_BY, RELATION_CHILD_OF, + RELATION_CONSUMES_API, RELATION_HAS_MEMBER, RELATION_MEMBER_OF, RELATION_OWNED_BY, @@ -138,6 +140,18 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { RELATION_PROVIDES_API, RELATION_API_PROVIDED_BY, ); + doEmit( + component.spec.providesApis, + { defaultKind: 'API', defaultNamespace: selfRef.namespace }, + RELATION_PROVIDES_API, + RELATION_API_PROVIDED_BY, + ); + doEmit( + component.spec.consumesApis, + { defaultKind: 'API', defaultNamespace: selfRef.namespace }, + RELATION_CONSUMES_API, + RELATION_API_CONSUMED_BY, + ); } /*