diff --git a/packages/catalog-model/src/EntityPolicies.ts b/packages/catalog-model/src/EntityPolicies.ts index cf23bcdc88..d761aa9f20 100644 --- a/packages/catalog-model/src/EntityPolicies.ts +++ b/packages/catalog-model/src/EntityPolicies.ts @@ -23,8 +23,8 @@ import { SchemaValidEntityPolicy, } from './entity'; import { - ComponentEntityV1beta1Policy, - LocationEntityV1beta1Policy, + ComponentEntityV1alpha1Policy, + LocationEntityV1alpha1Policy, } from './kinds'; import { EntityPolicy } from './types'; @@ -72,8 +72,8 @@ export class EntityPolicies implements EntityPolicy { new ReservedFieldsEntityPolicy(), ]), EntityPolicies.anyOf([ - new ComponentEntityV1beta1Policy(), - new LocationEntityV1beta1Policy(), + new ComponentEntityV1alpha1Policy(), + new LocationEntityV1alpha1Policy(), ]), ]); } diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1beta1.test.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts similarity index 81% rename from packages/catalog-model/src/kinds/ComponentEntityV1beta1.test.ts rename to packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts index a7988047b7..f1a4b3c2c0 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1beta1.test.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts @@ -16,17 +16,17 @@ import { EntityPolicy } from '../types'; import { - ComponentEntityV1beta1, - ComponentEntityV1beta1Policy, -} from './ComponentEntityV1beta1'; + ComponentEntityV1alpha1, + ComponentEntityV1alpha1Policy, +} from './ComponentEntityV1alpha1'; -describe('ComponentV1beta1Policy', () => { - let entity: ComponentEntityV1beta1; +describe('ComponentV1alpha1Policy', () => { + let entity: ComponentEntityV1alpha1; let policy: EntityPolicy; beforeEach(() => { entity = { - apiVersion: 'backstage.io/v1beta1', + apiVersion: 'backstage.io/v1alpha1', kind: 'Component', metadata: { name: 'test', @@ -37,15 +37,20 @@ describe('ComponentV1beta1Policy', () => { owner: 'me', }, }; - policy = new ComponentEntityV1beta1Policy(); + policy = new ComponentEntityV1alpha1Policy(); }); it('happy path: accepts valid data', async () => { await expect(policy.enforce(entity)).resolves.toBe(entity); }); + it('happy path: silently accepts v1beta1 as well', async () => { + (entity as any).apiVersion = 'backstage.io/v1beta1'; + await expect(policy.enforce(entity)).resolves.toBe(entity); + }); + it('rejects unknown apiVersion', async () => { - (entity as any).apiVersion = 'backstage.io/v1beta2'; + (entity as any).apiVersion = 'backstage.io/v1beta0'; await expect(policy.enforce(entity)).rejects.toThrow(/apiVersion/); }); @@ -64,7 +69,7 @@ describe('ComponentV1beta1Policy', () => { await expect(policy.enforce(entity)).rejects.toThrow(/type/); }); - it('rejects empty wrong type', async () => { + it('rejects empty type', async () => { (entity as any).spec.type = ''; await expect(policy.enforce(entity)).rejects.toThrow(/type/); }); @@ -79,7 +84,7 @@ describe('ComponentV1beta1Policy', () => { await expect(policy.enforce(entity)).rejects.toThrow(/lifecycle/); }); - it('rejects wrong empty', async () => { + it('rejects empty lifecycle', async () => { (entity as any).spec.lifecycle = ''; await expect(policy.enforce(entity)).rejects.toThrow(/lifecycle/); }); diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1beta1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts similarity index 72% rename from packages/catalog-model/src/kinds/ComponentEntityV1beta1.ts rename to packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts index 02bcffbd28..68cf0c61bb 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1beta1.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts @@ -18,11 +18,11 @@ import * as yup from 'yup'; import type { Entity } from '../entity/Entity'; import type { EntityPolicy } from '../types'; -const API_VERSION = 'backstage.io/v1beta1'; -const KIND = 'Component'; +const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const; +const KIND = 'Component' as const; -export interface ComponentEntityV1beta1 extends Entity { - apiVersion: typeof API_VERSION; +export interface ComponentEntityV1alpha1 extends Entity { + apiVersion: typeof API_VERSION[number]; kind: typeof KIND; spec: { type: string; @@ -31,11 +31,13 @@ export interface ComponentEntityV1beta1 extends Entity { }; } -export class ComponentEntityV1beta1Policy implements EntityPolicy { +export class ComponentEntityV1alpha1Policy implements EntityPolicy { private schema: yup.Schema; constructor() { - this.schema = yup.object>({ + this.schema = yup.object>({ + apiVersion: yup.string().required().oneOf(API_VERSION), + kind: yup.string().required().equals([KIND]), spec: yup .object({ type: yup.string().required().min(1), @@ -47,10 +49,6 @@ export class ComponentEntityV1beta1Policy implements EntityPolicy { } async enforce(envelope: Entity): Promise { - if (envelope.apiVersion !== API_VERSION || envelope.kind !== KIND) { - throw new Error('Unsupported apiVersion / kind'); - } - return await this.schema.validate(envelope, { strict: true }); } } diff --git a/packages/catalog-model/src/kinds/LocationEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/LocationEntityV1alpha1.test.ts new file mode 100644 index 0000000000..e3c8170af2 --- /dev/null +++ b/packages/catalog-model/src/kinds/LocationEntityV1alpha1.test.ts @@ -0,0 +1,103 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { EntityPolicy } from '../types'; +import { + LocationEntityV1alpha1, + LocationEntityV1alpha1Policy, +} from './LocationEntityV1alpha1'; + +describe('LocationV1alpha1Policy', () => { + let entity: LocationEntityV1alpha1; + let policy: EntityPolicy; + + beforeEach(() => { + entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + name: 'test', + }, + spec: { + type: 'github', + }, + }; + policy = new LocationEntityV1alpha1Policy(); + }); + + it('happy path: accepts valid data', async () => { + await expect(policy.enforce(entity)).resolves.toBe(entity); + }); + + it('happy path: silently accepts v1beta1 as well', async () => { + (entity as any).apiVersion = 'backstage.io/v1beta1'; + await expect(policy.enforce(entity)).resolves.toBe(entity); + }); + + it('rejects unknown apiVersion', async () => { + (entity as any).apiVersion = 'backstage.io/v1beta0'; + await expect(policy.enforce(entity)).rejects.toThrow(/apiVersion/); + }); + + it('rejects unknown kind', async () => { + (entity as any).kind = 'Wizard'; + await expect(policy.enforce(entity)).rejects.toThrow(/kind/); + }); + + it('rejects missing type', async () => { + delete (entity as any).spec.type; + await expect(policy.enforce(entity)).rejects.toThrow(/type/); + }); + + it('rejects wrong type', async () => { + (entity as any).spec.type = 7; + await expect(policy.enforce(entity)).rejects.toThrow(/type/); + }); + + it('rejects empty type', async () => { + (entity as any).spec.type = ''; + await expect(policy.enforce(entity)).rejects.toThrow(/type/); + }); + + it('accepts good target', async () => { + (entity as any).spec.target = + 'https://github.com/spotify/backstage/blob/master/plugins/catalog-backend/examples/artist-lookup-component.yaml'; + await expect(policy.enforce(entity)).resolves.toBe(entity); + }); + + it('rejects wrong target', async () => { + (entity as any).spec.target = 7; + await expect(policy.enforce(entity)).rejects.toThrow(/target/); + }); + + it('rejects empty target', async () => { + (entity as any).spec.target = ''; + await expect(policy.enforce(entity)).rejects.toThrow(/target/); + }); + + it('accepts good targets', async () => { + (entity as any).spec.targets = [ + 'https://github.com/spotify/backstage/blob/master/plugins/catalog-backend/examples/artist-lookup-component.yaml', + 'https://github.com/spotify/backstage/blob/master/plugins/catalog-backend/examples/playback-order-component.yaml', + ]; + await expect(policy.enforce(entity)).resolves.toBe(entity); + }); + + it('rejects wrong targets', async () => { + (entity as any).spec.targets = 7; + await expect(policy.enforce(entity)).rejects.toThrow(/targets/); + }); +}); diff --git a/packages/catalog-model/src/kinds/LocationEntityV1beta1.ts b/packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts similarity index 67% rename from packages/catalog-model/src/kinds/LocationEntityV1beta1.ts rename to packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts index e68bb00aa0..a69a3a574a 100644 --- a/packages/catalog-model/src/kinds/LocationEntityV1beta1.ts +++ b/packages/catalog-model/src/kinds/LocationEntityV1alpha1.ts @@ -18,11 +18,11 @@ import * as yup from 'yup'; import type { Entity } from '../entity/Entity'; import type { EntityPolicy } from '../types'; -const API_VERSION = 'backstage.io/v1beta1'; -const KIND = 'Location'; +const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const; +const KIND = 'Location' as const; -export interface LocationEntityV1beta1 extends Entity { - apiVersion: typeof API_VERSION; +export interface LocationEntityV1alpha1 extends Entity { + apiVersion: typeof API_VERSION[number]; kind: typeof KIND; spec: { type: string; @@ -31,15 +31,17 @@ export interface LocationEntityV1beta1 extends Entity { }; } -export class LocationEntityV1beta1Policy implements EntityPolicy { +export class LocationEntityV1alpha1Policy implements EntityPolicy { private schema: yup.Schema; constructor() { - this.schema = yup.object>({ + this.schema = yup.object>({ + apiVersion: yup.string().required().oneOf(API_VERSION), + kind: yup.string().required().equals([KIND]), spec: yup .object({ - type: yup.string().required(), - target: yup.string().notRequired(), + type: yup.string().required().min(1), + target: yup.string().notRequired().min(1), targets: yup.array(yup.string()).notRequired(), }) .required(), @@ -47,10 +49,6 @@ export class LocationEntityV1beta1Policy implements EntityPolicy { } async enforce(envelope: Entity): Promise { - if (envelope.apiVersion !== API_VERSION || envelope.kind !== KIND) { - throw new Error('Unsupported apiVersion / kind'); - } - return await this.schema.validate(envelope, { strict: true }); } } diff --git a/packages/catalog-model/src/kinds/index.ts b/packages/catalog-model/src/kinds/index.ts index 2d2a0744c5..85e22f7029 100644 --- a/packages/catalog-model/src/kinds/index.ts +++ b/packages/catalog-model/src/kinds/index.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -export { ComponentEntityV1beta1Policy } from './ComponentEntityV1beta1'; +export { ComponentEntityV1alpha1Policy } from './ComponentEntityV1alpha1'; export type { - ComponentEntityV1beta1 as ComponentEntity, - ComponentEntityV1beta1, -} from './ComponentEntityV1beta1'; -export { LocationEntityV1beta1Policy } from './LocationEntityV1beta1'; + ComponentEntityV1alpha1 as ComponentEntity, + ComponentEntityV1alpha1, +} from './ComponentEntityV1alpha1'; +export { LocationEntityV1alpha1Policy } from './LocationEntityV1alpha1'; export type { - LocationEntityV1beta1 as LocationEntity, - LocationEntityV1beta1, -} from './LocationEntityV1beta1'; + LocationEntityV1alpha1 as LocationEntity, + LocationEntityV1alpha1, +} from './LocationEntityV1alpha1'; diff --git a/plugins/catalog-backend/examples/artist-lookup-component.yaml b/plugins/catalog-backend/examples/artist-lookup-component.yaml index 5fbb073c1a..bb7d8d5767 100644 --- a/plugins/catalog-backend/examples/artist-lookup-component.yaml +++ b/plugins/catalog-backend/examples/artist-lookup-component.yaml @@ -1,4 +1,4 @@ -apiVersion: backstage.io/v1beta1 +apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: artist-lookup diff --git a/plugins/catalog-backend/examples/playback-order-component.yaml b/plugins/catalog-backend/examples/playback-order-component.yaml index 4ee959f48f..fa9f68e77a 100644 --- a/plugins/catalog-backend/examples/playback-order-component.yaml +++ b/plugins/catalog-backend/examples/playback-order-component.yaml @@ -1,4 +1,4 @@ -apiVersion: backstage.io/v1beta1 +apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: playback-order diff --git a/plugins/catalog-backend/examples/podcast-api-component.yaml b/plugins/catalog-backend/examples/podcast-api-component.yaml index c2c7ee34dd..2a27a0a2c5 100644 --- a/plugins/catalog-backend/examples/podcast-api-component.yaml +++ b/plugins/catalog-backend/examples/podcast-api-component.yaml @@ -1,4 +1,4 @@ -apiVersion: backstage.io/v1beta1 +apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: podcast-api diff --git a/plugins/catalog-backend/examples/queue-proxy-component.yaml b/plugins/catalog-backend/examples/queue-proxy-component.yaml index 8298b19f93..a9f41b8776 100644 --- a/plugins/catalog-backend/examples/queue-proxy-component.yaml +++ b/plugins/catalog-backend/examples/queue-proxy-component.yaml @@ -1,4 +1,4 @@ -apiVersion: backstage.io/v1beta1 +apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: queue-proxy diff --git a/plugins/catalog-backend/examples/searcher-component.yaml b/plugins/catalog-backend/examples/searcher-component.yaml index ecfd92f470..d82f6e42c7 100644 --- a/plugins/catalog-backend/examples/searcher-component.yaml +++ b/plugins/catalog-backend/examples/searcher-component.yaml @@ -1,4 +1,4 @@ -apiVersion: backstage.io/v1beta1 +apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: searcher diff --git a/plugins/catalog-backend/examples/shuffle-api-component.yaml b/plugins/catalog-backend/examples/shuffle-api-component.yaml index d45a06e030..ed0517835c 100644 --- a/plugins/catalog-backend/examples/shuffle-api-component.yaml +++ b/plugins/catalog-backend/examples/shuffle-api-component.yaml @@ -1,4 +1,4 @@ -apiVersion: backstage.io/v1beta1 +apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: shuffle-api diff --git a/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts b/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts index 6ab660a587..7c4b964b1b 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts @@ -14,10 +14,9 @@ * limitations under the License. */ -import type { Entity } from '@backstage/catalog-model'; -import { LOCATION_ANNOTATION } from '@backstage/catalog-model'; import { NotFoundError } from '@backstage/backend-common'; - +import { LOCATION_ANNOTATION } from '@backstage/catalog-model'; +import type { Entity } from '@backstage/catalog-model'; import type { Database, DbEntityResponse, EntityFilters } from '../database'; import type { EntitiesCatalog } from './types';