Add an entity fact retriever

Signed-off-by: Iain Billett <iain@roadie.io>
This commit is contained in:
Iain Billett
2021-11-02 17:47:09 +00:00
parent 3e6762ba3f
commit f091863f03
2 changed files with 203 additions and 0 deletions
@@ -0,0 +1,111 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 { entityFactRetriever } from './entityFactRetriever';
import { Entity } from '@backstage/catalog-model';
import {
PluginEndpointDiscovery,
getVoidLogger,
} from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import { CatalogListResponse } from '@backstage/catalog-client';
const getEntitiesMock = jest.fn();
jest.mock('@backstage/catalog-client', () => {
return {
CatalogClient: jest
.fn()
.mockImplementation(() => ({ getEntities: getEntitiesMock })),
};
});
const discovery: jest.Mocked<PluginEndpointDiscovery> = {
getBaseUrl: jest.fn(),
getExternalBaseUrl: jest.fn(),
};
const defaultEntityListResponse: CatalogListResponse<Entity> = {
items: [
{
apiVersion: 'backstage.io/v1beta1',
metadata: { name: 'service-with-owner' },
kind: 'Component',
spec: {
type: 'service',
lifecycle: 'test',
owner: 'group:team-a',
},
relations: [
{
type: 'ownedBy',
target: { name: 'team-a', kind: 'group', namespace: 'default' },
},
],
},
{
apiVersion: 'backstage.io/v1beta1',
metadata: {
name: 'service-without-owner',
},
kind: 'Component',
spec: {
type: 'service',
lifecycle: 'test',
},
},
],
};
const handlerContext = {
discovery,
logger: getVoidLogger(),
config: ConfigReader.fromConfigs([]),
};
describe('entityFactRetriever', () => {
beforeEach(() => {
getEntitiesMock.mockResolvedValue(defaultEntityListResponse);
});
afterEach(() => {
getEntitiesMock.mockClear();
});
describe('hasSpecWithOwner', () => {
describe('where the entity has an owner in the spec', () => {
it('returns true for hasSpecWithOwner', async () => {
const facts = await entityFactRetriever.handler(handlerContext);
expect(
facts.find(it => it.entity.name === 'service-with-owner'),
).toMatchObject({
facts: {
hasSpecWithOwner: true,
},
});
});
});
describe('where the entity does not have an owner in the spec', () => {
it('returns false for hasSpecWithOwner', async () => {
const facts = await entityFactRetriever.handler(handlerContext);
expect(
facts.find(it => it.entity.name === 'service-without-owner'),
).toMatchObject({
facts: {
hasSpecWithOwner: false,
},
});
});
});
});
});
@@ -0,0 +1,92 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 { FactRetrieverContext } from '@backstage/plugin-tech-insights-node';
import { CatalogClient } from '@backstage/catalog-client';
import { RELATION_OWNED_BY } from '@backstage/catalog-model';
const applicableEntityKinds = [
'component',
'api',
'template',
'domain',
'resource',
'system',
];
const entityKindFilter = { field: 'kind', values: applicableEntityKinds };
export const entityFactRetriever = {
id: 'entityRetriever',
version: '0.0.1',
entityFilter: [
{
field: 'kind',
values: applicableEntityKinds,
},
],
schema: {
hasSpecWithOwner: {
type: 'boolean',
description: 'The spec.owner field is set',
},
hasSpecWithGroupOwner: {
type: 'boolean',
description: 'The spec.owner field is set and refers to a group',
},
hasOwnedByRelation: {
type: 'boolean',
description: 'The entity has an owned_by relation',
},
hasOwnedByRelationWithGroupTarget: {
type: 'boolean',
description: 'The entity has an owned_by relation which targets a group',
},
},
handler: async ({ discovery }: FactRetrieverContext) => {
const catalogClient = new CatalogClient({
discoveryApi: discovery,
});
const entities = await catalogClient.getEntities({
filter: entityKindFilter,
});
return entities.items.map(it => {
return {
entity: {
namespace: it.metadata.namespace!!,
kind: it.kind,
name: it.metadata.name,
},
facts: {
hasSpecWithOwner: Boolean(it.spec?.owner),
hasSpecWithGroupOwner: Boolean(it.spec?.owner && it.spec?.owner),
hasOwnedByRelation: Boolean(
it.relations &&
it.relations.find(({ type }) => type === RELATION_OWNED_BY),
),
hasOwnedByRelationWithGroupTarget: Boolean(
it.relations &&
it.relations.find(
({ type, target }) =>
type === RELATION_OWNED_BY && target.kind === 'group',
),
),
},
};
});
},
};