Merge pull request #16210 from RoadieHQ/codeowners-processor-monorepo-support
feat: CodeOwnersProcessor monorepo support
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': minor
|
||||
---
|
||||
|
||||
Add monorepo support to CodeOwnersProccesor.
|
||||
@@ -17,11 +17,13 @@
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { CodeOwnersProcessor } from './CodeOwnersProcessor';
|
||||
import { LocationSpec } from '@backstage/plugin-catalog-node';
|
||||
import { LocationSpec } from '@backstage/plugin-catalog-common';
|
||||
|
||||
const mockCodeOwnersText = () => `
|
||||
* @acme/team-foo @acme/team-bar
|
||||
/docs @acme/team-bar
|
||||
* @acme/team-foo @acme/team-bar
|
||||
/docs @acme/team-docs
|
||||
/plugins/catalog-* @backstage/maintainers @backstage/catalog-core
|
||||
**/logs @logs-owner
|
||||
`;
|
||||
|
||||
describe('CodeOwnersProcessor', () => {
|
||||
@@ -34,8 +36,12 @@ describe('CodeOwnersProcessor', () => {
|
||||
});
|
||||
|
||||
describe('preProcessEntity', () => {
|
||||
const setupTest = ({ kind = 'Component', spec = {} } = {}) => {
|
||||
const entity = { kind, spec };
|
||||
const setupTest = ({
|
||||
kind = 'Component',
|
||||
spec = {},
|
||||
metadata = {},
|
||||
} = {}) => {
|
||||
const entity = { kind, spec, metadata };
|
||||
const config = new ConfigReader({});
|
||||
const reader = {
|
||||
read: jest.fn(),
|
||||
@@ -101,5 +107,44 @@ describe('CodeOwnersProcessor', () => {
|
||||
spec: { owner: 'team-foo' },
|
||||
});
|
||||
});
|
||||
it('should match owner based on the targetUrl', async () => {
|
||||
const { entity, processor } = setupTest();
|
||||
|
||||
const result = await processor.preProcessEntity(
|
||||
entity as any,
|
||||
mockLocation({ basePath: 'docs/' }),
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
...entity,
|
||||
spec: { owner: 'team-docs' },
|
||||
});
|
||||
});
|
||||
it('should match wildcard pattern', async () => {
|
||||
const { entity, processor } = setupTest();
|
||||
|
||||
const result = await processor.preProcessEntity(
|
||||
entity as any,
|
||||
mockLocation({ basePath: 'plugins/catalog-foo' }),
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
...entity,
|
||||
spec: { owner: 'maintainers' },
|
||||
});
|
||||
});
|
||||
it('should match glob pattern', async () => {
|
||||
const { entity, processor } = setupTest();
|
||||
|
||||
const result = await processor.preProcessEntity(
|
||||
entity as any,
|
||||
mockLocation({ basePath: 'plugins/catalog-foo/logs/1.txt' }),
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
...entity,
|
||||
spec: { owner: 'User:logs-owner' },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,7 +70,7 @@ export async function findCodeOwnerByTarget(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const owner = resolveCodeOwner(contents);
|
||||
const owner = resolveCodeOwner(contents, targetUrl);
|
||||
|
||||
return owner;
|
||||
}
|
||||
|
||||
@@ -18,12 +18,33 @@ import { normalizeCodeOwner, resolveCodeOwner } from './resolve';
|
||||
|
||||
const mockCodeOwnersText = () => `
|
||||
* @acme/team-foo @acme/team-bar
|
||||
/docs @acme/team-bar
|
||||
/docs @acme/team-docs
|
||||
`;
|
||||
|
||||
describe('resolveCodeOwner', () => {
|
||||
it('should parse the codeowners file', () => {
|
||||
expect(resolveCodeOwner(mockCodeOwnersText())).toBe('team-foo');
|
||||
expect(
|
||||
resolveCodeOwner(
|
||||
mockCodeOwnersText(),
|
||||
'https://github.com/can/be/tree/anything/catalog-info.yaml',
|
||||
),
|
||||
).toBe('team-foo');
|
||||
});
|
||||
it('should include the codeowners path into the provided pattern', () => {
|
||||
expect(
|
||||
resolveCodeOwner(
|
||||
mockCodeOwnersText(),
|
||||
'https://github.com/acme/repo/tree/main/docs/catalog-info.yaml',
|
||||
),
|
||||
).toBe('team-docs');
|
||||
});
|
||||
it('should match only in resource path', () => {
|
||||
expect(
|
||||
resolveCodeOwner(
|
||||
mockCodeOwnersText(),
|
||||
'https://github.com/acme/repo/tree/docs/catalog-info.yaml',
|
||||
),
|
||||
).toBe('team-foo');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
*/
|
||||
|
||||
import * as codeowners from 'codeowners-utils';
|
||||
import { CodeOwnersEntry } from 'codeowners-utils';
|
||||
import { filter, get, head, pipe, reverse } from 'lodash/fp';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
|
||||
const USER_PATTERN = /^@.*/;
|
||||
const GROUP_PATTERN = /^@.*\/.*/;
|
||||
@@ -24,18 +23,14 @@ const EMAIL_PATTERN = /^.*@.*\..*$/;
|
||||
|
||||
export function resolveCodeOwner(
|
||||
contents: string,
|
||||
pattern = '*',
|
||||
catalogInfoFileUrl: string,
|
||||
): string | undefined {
|
||||
const owners = codeowners.parse(contents);
|
||||
const codeOwnerEntries = codeowners.parse(contents);
|
||||
|
||||
return pipe(
|
||||
filter((e: CodeOwnersEntry) => e.pattern === pattern),
|
||||
reverse,
|
||||
head,
|
||||
get('owners'),
|
||||
head,
|
||||
normalizeCodeOwner,
|
||||
)(owners);
|
||||
const { filepath } = parseGitUrl(catalogInfoFileUrl);
|
||||
const match = codeowners.matchFile(filepath, codeOwnerEntries);
|
||||
|
||||
return match ? normalizeCodeOwner(match.owners[0]) : undefined;
|
||||
}
|
||||
|
||||
export function normalizeCodeOwner(owner: string) {
|
||||
|
||||
Reference in New Issue
Block a user