diff --git a/packages/catalog-model/src/EntityPolicies.ts b/packages/catalog-model/src/EntityPolicies.ts index d761aa9f20..3dc7deae7c 100644 --- a/packages/catalog-model/src/EntityPolicies.ts +++ b/packages/catalog-model/src/EntityPolicies.ts @@ -25,6 +25,7 @@ import { import { ComponentEntityV1alpha1Policy, LocationEntityV1alpha1Policy, + TemplateEntityV1alpha1Policy, } from './kinds'; import { EntityPolicy } from './types'; @@ -74,6 +75,7 @@ export class EntityPolicies implements EntityPolicy { EntityPolicies.anyOf([ new ComponentEntityV1alpha1Policy(), new LocationEntityV1alpha1Policy(), + new TemplateEntityV1alpha1Policy(), ]), ]); } diff --git a/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.test.ts new file mode 100644 index 0000000000..d2e455145f --- /dev/null +++ b/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.test.ts @@ -0,0 +1,74 @@ +/* + * 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 { + TemplateEntityV1alpha1, + TemplateEntityV1alpha1Policy, +} from './TemplateEntityV1alpha1'; + +describe('TemplateEntityV1alpah1', () => { + let entity: TemplateEntityV1alpha1; + let policy: EntityPolicy; + + beforeEach(() => { + entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Template', + metadata: { + name: 'test', + }, + spec: { + type: 'cookiecutter', + }, + }; + policy = new TemplateEntityV1alpha1Policy(); + }); + + it('happy path: accepts valid data', async () => { + await expect(policy.enforce(entity)).resolves.toBe(entity); + }); + + it('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('acceptps any other type', async () => { + (entity as any).spec.type = 'hallo'; + await expect(policy.enforce(entity)).resolves.toBe(entity); + }); + + it('rejects empty type', async () => { + (entity as any).spec.type = ''; + await expect(policy.enforce(entity)).rejects.toThrow(/type/); + }); +}); diff --git a/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.ts b/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.ts new file mode 100644 index 0000000000..090c892294 --- /dev/null +++ b/packages/catalog-model/src/kinds/TemplateEntityV1alpha1.ts @@ -0,0 +1,50 @@ +/* + * 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 * as yup from 'yup'; +import type { Entity } from '../entity/Entity'; +import type { EntityPolicy } from '../types'; + +const API_VERSION = ['backstage.io/v1alpha1', 'backstage.io/v1beta1'] as const; +const KIND = 'Template' as const; + +export interface TemplateEntityV1alpha1 extends Entity { + apiVersion: typeof API_VERSION[number]; + kind: typeof KIND; + spec: { + type: string; + }; +} + +export class TemplateEntityV1alpha1Policy implements EntityPolicy { + private schema: yup.Schema; + + constructor() { + 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), + }) + .required(), + }); + } + + async enforce(envelope: Entity): Promise { + 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 85e22f7029..b5fad184d4 100644 --- a/packages/catalog-model/src/kinds/index.ts +++ b/packages/catalog-model/src/kinds/index.ts @@ -24,3 +24,8 @@ export type { LocationEntityV1alpha1 as LocationEntity, LocationEntityV1alpha1, } from './LocationEntityV1alpha1'; +export { TemplateEntityV1alpha1Policy } from './TemplateEntityV1alpha1'; +export type { + TemplateEntityV1alpha1 as TemplateEntity, + TemplateEntityV1alpha1, +} from './TemplateEntityV1alpha1';