From fb19e0241dc831ba238457df10d8f89234cc9c49 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 16:01:34 +0100 Subject: [PATCH 1/5] 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, + ); } /* From ab94c9542fdc32fe9407daad9f477dbface59099 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 16:03:02 +0100 Subject: [PATCH 2/5] Add changeset --- .changeset/good-cycles-shave.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/good-cycles-shave.md diff --git a/.changeset/good-cycles-shave.md b/.changeset/good-cycles-shave.md new file mode 100644 index 0000000000..90a9410b9c --- /dev/null +++ b/.changeset/good-cycles-shave.md @@ -0,0 +1,6 @@ +--- +'@backstage/catalog-model': patch +'@backstage/plugin-catalog-backend': patch +--- + +Add `providesApis` and `consumesApis` to the component entity spec. From 069cda35f43c1a775e98d07bf548eccb1d075821 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 16:21:08 +0100 Subject: [PATCH 3/5] Deprecate implementsApis --- .changeset/short-singers-serve.md | 15 +++++++++++++++ .../software-catalog/descriptor-format.md | 8 ++++++-- .../src/kinds/ComponentEntityV1alpha1.ts | 5 +++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 .changeset/short-singers-serve.md diff --git a/.changeset/short-singers-serve.md b/.changeset/short-singers-serve.md new file mode 100644 index 0000000000..ae8e8d171c --- /dev/null +++ b/.changeset/short-singers-serve.md @@ -0,0 +1,15 @@ +--- +'@backstage/catalog-model': patch +--- + +Marked the field `spec.implementsApis` on `Component` entities for deprecation on Dec 14th, 2020. + +Code that consumes these fields should remove those usages as soon as possible as migrate to using +relations instead. Producers should fill the field `spec.providesApis` instead, which has the same +semantic. + +After Dec 14th, the fields will be removed from types and classes of the Backstage repository. At +the first release after that, they will not be present in released packages either. + +If your catalog-info.yaml files still contain this fields after the deletion, they will still be +valid and your ingestion will not break, but they won't be visible in the types for consuming code. diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index c86be1be83..438f874990 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -445,14 +445,18 @@ group of people in an organizational structure. ### `spec.implementsApis` [optional] +**NOTE**: This field was marked for deprecation on Nov 25nd, 2020. It will be +removed entirely from the model on Dec 14th, 2020 in the repository and will not +be present in released packages following the next release after that. Please +update your code to not consume this field before the removal date. + Links APIs that are implemented 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`. -This field has the same behavior as `spec.providesApis` and will be deprecated -in the future. +This field has the same behavior as `spec.providesApis`. ### `spec.providesApis` [optional] diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts index 97c9140e61..5fd4f88537 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts @@ -52,6 +52,11 @@ export interface ComponentEntityV1alpha1 extends Entity { type: string; lifecycle: string; owner: string; + /** + * @deprecated This field will disappear on Dec 14th, 2020. Please remove + * any consuming code. The new field providesApis provides the + * same functionality like before. + */ implementsApis?: string[]; providesApis?: string[]; consumesApis?: string[]; From 6f70ed7a95e94d40e2eb5634b851c0ea6487e664 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 17:23:54 +0100 Subject: [PATCH 4/5] Replace usage of implementsApis with relations --- .changeset/fifty-nails-pay.md | 6 ++++++ .changeset/short-singers-serve.md | 2 +- .../MissingImplementsApisEmptyState.tsx | 7 +++---- plugins/api-docs/src/catalog/Router.tsx | 5 +++-- .../api-docs/src/components/useComponentApiNames.ts | 12 ++++++++++-- .../catalog/src/components/AboutCard/AboutCard.tsx | 7 +++++-- 6 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 .changeset/fifty-nails-pay.md diff --git a/.changeset/fifty-nails-pay.md b/.changeset/fifty-nails-pay.md new file mode 100644 index 0000000000..5880d9a8cc --- /dev/null +++ b/.changeset/fifty-nails-pay.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog': patch +--- + +Replace usage of implementsApis with relations diff --git a/.changeset/short-singers-serve.md b/.changeset/short-singers-serve.md index ae8e8d171c..c2f1c1914f 100644 --- a/.changeset/short-singers-serve.md +++ b/.changeset/short-singers-serve.md @@ -4,7 +4,7 @@ Marked the field `spec.implementsApis` on `Component` entities for deprecation on Dec 14th, 2020. -Code that consumes these fields should remove those usages as soon as possible as migrate to using +Code that consumes these fields should remove those usages as soon as possible and migrate to using relations instead. Producers should fill the field `spec.providesApis` instead, which has the same semantic. diff --git a/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx b/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx index 9160aca514..f13848bd19 100644 --- a/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx +++ b/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx @@ -28,7 +28,7 @@ spec: type: service lifecycle: production owner: guest - implementsApis: + providesApis: - example-api `; @@ -49,8 +49,7 @@ export const MissingImplementsApisEmptyState = () => { description={ Components can implement APIs that are displayed on this page. You - need to fill the implementsApis field to enable this - tool. + need to fill the providesApis field to enable this tool. } action={ @@ -71,7 +70,7 @@ export const MissingImplementsApisEmptyState = () => { diff --git a/plugins/api-docs/src/catalog/Router.tsx b/plugins/api-docs/src/catalog/Router.tsx index d3fe7f7553..6991ad4b57 100644 --- a/plugins/api-docs/src/catalog/Router.tsx +++ b/plugins/api-docs/src/catalog/Router.tsx @@ -15,14 +15,15 @@ */ import React from 'react'; -import { Entity } from '@backstage/catalog-model'; +import { Entity, RELATION_PROVIDES_API } from '@backstage/catalog-model'; import { Route, Routes } from 'react-router'; import { catalogRoute } from '../routes'; import { EntityPageApi } from './EntityPageApi'; import { MissingImplementsApisEmptyState } from './MissingImplementsApisEmptyState'; const isPluginApplicableToEntity = (entity: Entity) => { - return ((entity.spec?.implementsApis as string[]) || []).length > 0; + // TODO: Als support RELATION_CONSUMES_API + return entity.relations?.some(r => r.type === RELATION_PROVIDES_API); }; export const Router = ({ entity }: { entity: Entity }) => diff --git a/plugins/api-docs/src/components/useComponentApiNames.ts b/plugins/api-docs/src/components/useComponentApiNames.ts index 0eabe2b6c7..1303967895 100644 --- a/plugins/api-docs/src/components/useComponentApiNames.ts +++ b/plugins/api-docs/src/components/useComponentApiNames.ts @@ -14,8 +14,16 @@ * limitations under the License. */ -import { ComponentEntity } from '@backstage/catalog-model'; +import { + ComponentEntity, + RELATION_PROVIDES_API, +} from '@backstage/catalog-model'; export const useComponentApiNames = (entity: ComponentEntity) => { - return (entity.spec?.implementsApis as string[]) || []; + // TODO: This code doesn't handle namespaces and kinds correctly, but will be removed soon + return ( + entity.relations + ?.filter(r => r.type === RELATION_PROVIDES_API) + ?.map(r => r.target.name) || [] + ); }; diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 89630d6f10..49653fe981 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -18,6 +18,7 @@ import { Entity, ENTITY_DEFAULT_NAMESPACE, RELATION_OWNED_BY, + RELATION_PROVIDES_API, serializeEntityRef, } from '@backstage/catalog-model'; import { @@ -111,6 +112,8 @@ type AboutCardProps = { export function AboutCard({ entity, variant }: AboutCardProps) { const classes = useStyles(); const codeLink = getCodeLinkInfo(entity); + // TODO: Also support RELATION_CONSUMES_API here + const hasApis = entity.relations?.some(r => r.type === RELATION_PROVIDES_API); return ( @@ -146,9 +149,9 @@ export function AboutCard({ entity, variant }: AboutCardProps) { }/${entity.kind}/${entity.metadata.name}`} /> } href="api" /> From 032aba5920514011ca74f965b29fec9782f149e0 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 26 Nov 2020 16:40:18 +0100 Subject: [PATCH 5/5] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- .changeset/short-singers-serve.md | 4 ++-- plugins/api-docs/README.md | 2 +- plugins/api-docs/src/catalog/Router.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.changeset/short-singers-serve.md b/.changeset/short-singers-serve.md index c2f1c1914f..f1e90f3b55 100644 --- a/.changeset/short-singers-serve.md +++ b/.changeset/short-singers-serve.md @@ -11,5 +11,5 @@ semantic. After Dec 14th, the fields will be removed from types and classes of the Backstage repository. At the first release after that, they will not be present in released packages either. -If your catalog-info.yaml files still contain this fields after the deletion, they will still be -valid and your ingestion will not break, but they won't be visible in the types for consuming code. +If your catalog-info.yaml files still contain this field after the deletion, they will still be +valid and your ingestion will not break, but they won't be visible in the types for consuming code, and the expected relations will not be generated based on them either.``` diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md index f5b9a79a85..182ee5a882 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 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. +To link that a component provides or consumes an API, see the [`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) properties on the Component kind. ## Links diff --git a/plugins/api-docs/src/catalog/Router.tsx b/plugins/api-docs/src/catalog/Router.tsx index 6991ad4b57..64c074fc46 100644 --- a/plugins/api-docs/src/catalog/Router.tsx +++ b/plugins/api-docs/src/catalog/Router.tsx @@ -22,7 +22,7 @@ import { EntityPageApi } from './EntityPageApi'; import { MissingImplementsApisEmptyState } from './MissingImplementsApisEmptyState'; const isPluginApplicableToEntity = (entity: Entity) => { - // TODO: Als support RELATION_CONSUMES_API + // TODO: Also support RELATION_CONSUMES_API return entity.relations?.some(r => r.type === RELATION_PROVIDES_API); };