feat: use the new UrlReader in the CodeOwnersProcessor

This commit is contained in:
Oliver Sand
2020-10-08 14:36:22 +02:00
parent c6d1f4846f
commit 512d709736
4 changed files with 60 additions and 68 deletions
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': minor
---
Use the new `UrlReader` in the `CodeOwnersProcessor`.
@@ -125,7 +125,7 @@ export class LocationReaders implements LocationReader {
new UrlReaderProcessor(options),
new YamlProcessor(),
PlaceholderProcessor.default({ reader: options.reader }),
new CodeOwnersProcessor(),
new CodeOwnersProcessor({ reader: options.reader }),
new EntityPolicyProcessor(entityPolicy),
new LocationRefProcessor(),
new AnnotateLocationEntityProcessor(),
@@ -17,7 +17,7 @@
import { LocationSpec } from '@backstage/catalog-model';
import { CodeOwnersEntry } from 'codeowners-utils';
import {
buildCodeOwnerLocation,
buildCodeOwnerUrl,
buildUrl,
CodeOwnersProcessor,
findPrimaryCodeOwner,
@@ -28,18 +28,18 @@ import {
} from './CodeOwnersProcessor';
describe(CodeOwnersProcessor, () => {
const mockUrl = ({ basePath = '' } = {}): string =>
`https://github.com/spotify/backstage/blob/master/${basePath}catalog-info.yaml`;
const mockLocation = ({
basePath = '',
type = 'github',
} = {}): LocationSpec => ({
type,
target: `https://github.com/spotify/backstage/blob/master/${basePath}catalog-info.yaml`,
target: mockUrl({ basePath }),
});
const mockReadLocation = (basePath = '') => ({
type: 'github',
target: `https://github.com/spotify/backstage/blob/master/${basePath}CODEOWNERS`,
});
const mockReadUrl = (basePath = '') =>
`https://github.com/spotify/backstage/blob/master/${basePath}CODEOWNERS`;
const mockGitUri = (codeOwnersPath: string = '') => {
return {
@@ -99,27 +99,20 @@ describe(CodeOwnersProcessor, () => {
});
});
describe(buildCodeOwnerLocation, () => {
it('should builds a location spec to the codeowners', () => {
expect(
buildCodeOwnerLocation(mockLocation(), '/docs/CODEOWNERS'),
).toEqual({
type: 'github',
target:
'https://github.com/spotify/backstage/blob/master/docs/CODEOWNERS',
});
describe(buildCodeOwnerUrl, () => {
it('should build a location spec to the codeowners', () => {
expect(buildCodeOwnerUrl(mockUrl(), '/docs/CODEOWNERS')).toEqual(
'https://github.com/spotify/backstage/blob/master/docs/CODEOWNERS',
);
});
it('should handle nested paths from original location spec', () => {
expect(
buildCodeOwnerLocation(
mockLocation({ basePath: 'packages/foo/' }),
buildCodeOwnerUrl(
mockUrl({ basePath: 'packages/foo/' }),
'/CODEOWNERS',
),
).toEqual({
type: 'github',
target: 'https://github.com/spotify/backstage/blob/master/CODEOWNERS',
});
).toEqual('https://github.com/spotify/backstage/blob/master/CODEOWNERS');
});
});
@@ -158,15 +151,17 @@ describe(CodeOwnersProcessor, () => {
const read = jest
.fn()
.mockResolvedValue(mockReadResult({ data: ownersText }));
const result = await findRawCodeOwners(mockLocation(), read);
const reader = { read };
const result = await findRawCodeOwners(mockLocation(), reader);
expect(result).toEqual(ownersText);
});
it('should raise error when no codeowner', async () => {
const read = jest.fn().mockRejectedValue(mockReadResult());
const reader = { read };
await expect(
findRawCodeOwners(mockLocation(), read),
findRawCodeOwners(mockLocation(), reader),
).rejects.toBeInstanceOf(Error);
});
@@ -177,13 +172,14 @@ describe(CodeOwnersProcessor, () => {
.mockImplementationOnce(() => mockReadResult({ error: 'foo' }))
.mockImplementationOnce(() => mockReadResult({ error: 'bar' }))
.mockResolvedValue(mockReadResult({ data: ownersText }));
const reader = { read };
const result = await findRawCodeOwners(mockLocation(), read);
const result = await findRawCodeOwners(mockLocation(), reader);
expect(read.mock.calls.length).toBe(3);
expect(read.mock.calls[0]).toEqual([mockReadLocation('.github/')]);
expect(read.mock.calls[1]).toEqual([mockReadLocation('')]);
expect(read.mock.calls[2]).toEqual([mockReadLocation('docs/')]);
expect(read.mock.calls[0]).toEqual([mockReadUrl('.github/')]);
expect(read.mock.calls[1]).toEqual([mockReadUrl('')]);
expect(read.mock.calls[2]).toEqual([mockReadUrl('docs/')]);
expect(result).toEqual(ownersText);
});
});
@@ -193,7 +189,9 @@ describe(CodeOwnersProcessor, () => {
const read = jest
.fn()
.mockResolvedValue(mockReadResult({ data: mockCodeOwnersText() }));
const owner = await resolveCodeOwner(mockLocation(), read);
const reader = { read };
const owner = await resolveCodeOwner(mockLocation(), reader);
expect(owner).toBe('backstage-core');
});
@@ -201,9 +199,10 @@ describe(CodeOwnersProcessor, () => {
const read = jest
.fn()
.mockImplementation(() => mockReadResult({ error: 'error: foo' }));
const reader = { read };
await expect(
resolveCodeOwner(mockLocation(), read),
resolveCodeOwner(mockLocation(), reader),
).rejects.toBeInstanceOf(Error);
});
});
@@ -211,10 +210,11 @@ describe(CodeOwnersProcessor, () => {
describe(CodeOwnersProcessor, () => {
const setupTest = ({ kind = 'Component', spec = {} } = {}) => {
const entity = { kind, spec };
const processor = new CodeOwnersProcessor();
const read = jest
.fn()
.mockResolvedValue(mockReadResult({ data: mockCodeOwnersText() }));
const reader = { read };
const processor = new CodeOwnersProcessor({ reader });
return { entity, processor, read };
};
@@ -227,8 +227,6 @@ describe(CodeOwnersProcessor, () => {
const result = await processor.processEntity(
entity as any,
mockLocation(),
null as any,
null as any,
);
expect(result).toEqual(entity);
@@ -240,8 +238,6 @@ describe(CodeOwnersProcessor, () => {
const result = await processor.processEntity(
entity as any,
mockLocation({ type: 'url' }),
null as any,
null as any,
);
expect(result).toEqual(entity);
@@ -253,21 +249,17 @@ describe(CodeOwnersProcessor, () => {
const result = await processor.processEntity(
entity as any,
mockLocation(),
null as any,
null as any,
);
expect(result).toEqual(entity);
});
it('should set owner from codeowner', async () => {
const { entity, processor, read } = setupTest();
const { entity, processor } = setupTest();
const result = await processor.processEntity(
entity as any,
mockLocation(),
null as any,
read,
);
expect(result).toEqual({
@@ -14,12 +14,9 @@
* limitations under the License.
*/
import { UrlReader } from '@backstage/backend-common';
import { Entity, LocationSpec } from '@backstage/catalog-model';
import {
LocationProcessor,
LocationProcessorEmit,
LocationProcessorRead,
} from './types';
import { LocationProcessor } from './types';
import * as codeowners from 'codeowners-utils';
import { CodeOwnersEntry } from 'codeowners-utils';
import parseGitUri from 'git-url-parse';
@@ -37,13 +34,14 @@ const ALLOWED_LOCATION_TYPES = [
'gitlab/api',
];
type Options = {
reader: UrlReader;
};
export class CodeOwnersProcessor implements LocationProcessor {
async processEntity(
entity: Entity,
location: LocationSpec,
_emit: LocationProcessorEmit,
read: LocationProcessorRead,
): Promise<Entity> {
constructor(private readonly options: Options) {}
async processEntity(entity: Entity, location: LocationSpec): Promise<Entity> {
// Only continue if the owner is not set
if (
!entity ||
@@ -54,7 +52,7 @@ export class CodeOwnersProcessor implements LocationProcessor {
return entity;
}
const owner = await resolveCodeOwner(location, read);
const owner = await resolveCodeOwner(location, this.options.reader);
return {
...entity,
@@ -65,9 +63,9 @@ export class CodeOwnersProcessor implements LocationProcessor {
export async function resolveCodeOwner(
location: LocationSpec,
read: LocationProcessorRead,
reader: UrlReader,
): Promise<string | undefined> {
const ownersText = await findRawCodeOwners(location, read);
const ownersText = await findRawCodeOwners(location, reader);
if (!ownersText) {
throw Error(`Unable to find codeowners file for: ${location.target}`);
@@ -80,15 +78,14 @@ export async function resolveCodeOwner(
export async function findRawCodeOwners(
location: LocationSpec,
read: LocationProcessorRead,
reader: UrlReader,
): Promise<string | undefined> {
const readOwnerLocation = async (basePath: string): Promise<string> => {
const ownerLocation = buildCodeOwnerLocation(
location,
const ownerUrl = buildCodeOwnerUrl(
location.target,
`${basePath}/CODEOWNERS`,
);
const data = await read(ownerLocation);
const data = await reader.read(ownerUrl);
return data.toString();
};
@@ -101,6 +98,13 @@ export async function findRawCodeOwners(
]);
}
export function buildCodeOwnerUrl(
basePath: string,
codeOwnersPath: string,
): string {
return buildUrl({ ...parseGitUri(basePath), codeOwnersPath });
}
export function parseCodeOwners(ownersText: string) {
return codeowners.parse(ownersText);
}
@@ -128,15 +132,6 @@ export function normalizeCodeOwner(owner: string) {
return owner;
}
export function buildCodeOwnerLocation(
location: LocationSpec,
codeOwnersPath: string,
): LocationSpec {
const { type, target } = location;
return { type, target: buildUrl({ ...parseGitUri(target), codeOwnersPath }) };
}
export function buildUrl({
protocol = 'https',
source = 'github.com',