Add ability to use topic for inclusion or exclusion

Signed-off-by: Marcus Crane <marcus.crane@lightspeedhq.com>
This commit is contained in:
Marcus Crane
2022-08-28 15:07:27 +12:00
parent 4c30622243
commit 8de87de128
4 changed files with 191 additions and 3 deletions
@@ -105,7 +105,91 @@ describe('GitHubEntityProvider', () => {
);
});
it('apply full update on scheduled execution', async () => {
it('apply full update on scheduled execution with basic filters', async () => {
const config = new ConfigReader({
catalog: {
providers: {
github: {
myProvider: {
organization: 'test-org',
catalogPath: 'custom/path/catalog-custom.yaml',
filters: {
branch: 'main',
repository: 'test-.*',
},
},
},
},
},
});
const schedule = new PersistingTaskRunner();
const entityProviderConnection: EntityProviderConnection = {
applyMutation: jest.fn(),
};
const provider = GitHubEntityProvider.fromConfig(config, {
logger,
schedule,
})[0];
const mockGetOrganizationRepositories = jest.spyOn(
helpers,
'getOrganizationRepositories',
);
mockGetOrganizationRepositories.mockReturnValue(
Promise.resolve({
repositories: [
{
name: 'test-repo',
url: 'https://github.com/test-org/test-repo',
repositoryTopics: { nodes: [] },
isArchived: false,
defaultBranchRef: {
name: 'main',
},
},
],
}),
);
await provider.connect(entityProviderConnection);
const taskDef = schedule.getTasks()[0];
expect(taskDef.id).toEqual('github-provider:myProvider:refresh');
await (taskDef.fn as () => Promise<void>)();
const url = `https://github.com/test-org/test-repo/blob/main/custom/path/catalog-custom.yaml`;
const expectedEntities = [
{
entity: {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Location',
metadata: {
annotations: {
'backstage.io/managed-by-location': `url:${url}`,
'backstage.io/managed-by-origin-location': `url:${url}`,
},
name: 'generated-5e4b9498097f15434e88c477cfba6c079aa8ca7f',
},
spec: {
presence: 'optional',
target: `${url}`,
type: 'url',
},
},
locationKey: 'github-provider:myProvider',
},
];
expect(entityProviderConnection.applyMutation).toBeCalledTimes(1);
expect(entityProviderConnection.applyMutation).toBeCalledWith({
type: 'full',
entities: expectedEntities,
});
});
it('apply full update on scheduled execution with topic exclusion', async () => {
const config = new ConfigReader({
catalog: {
providers: {
@@ -166,6 +250,75 @@ describe('GitHubEntityProvider', () => {
expect(taskDef.id).toEqual('github-provider:myProvider:refresh');
await (taskDef.fn as () => Promise<void>)();
expect(entityProviderConnection.applyMutation).toBeCalledTimes(1);
expect(entityProviderConnection.applyMutation).toBeCalledWith({
type: 'full',
entities: [],
});
});
it('apply full update on scheduled execution with topic inclusion', async () => {
const config = new ConfigReader({
catalog: {
providers: {
github: {
myProvider: {
organization: 'test-org',
catalogPath: 'custom/path/catalog-custom.yaml',
filters: {
branch: 'main',
repository: 'test-.*',
topic: 'backstage-include',
topicIncludesIfMatch: true,
},
},
},
},
},
});
const schedule = new PersistingTaskRunner();
const entityProviderConnection: EntityProviderConnection = {
applyMutation: jest.fn(),
};
const provider = GitHubEntityProvider.fromConfig(config, {
logger,
schedule,
})[0];
const mockGetOrganizationRepositories = jest.spyOn(
helpers,
'getOrganizationRepositories',
);
mockGetOrganizationRepositories.mockReturnValue(
Promise.resolve({
repositories: [
{
name: 'test-repo',
url: 'https://github.com/test-org/test-repo',
repositoryTopics: {
nodes: [
{
topic: { name: 'backstage-include' },
},
],
},
isArchived: false,
defaultBranchRef: {
name: 'main',
},
},
],
}),
);
await provider.connect(entityProviderConnection);
const taskDef = schedule.getTasks()[0];
expect(taskDef.id).toEqual('github-provider:myProvider:refresh');
await (taskDef.fn as () => Promise<void>)();
const url = `https://github.com/test-org/test-repo/blob/main/custom/path/catalog-custom.yaml`;
const expectedEntities = [
{
@@ -184,6 +184,7 @@ export class GitHubEntityProvider implements EntityProvider {
private matchesFilters(repositories: Repository[]) {
const repositoryFilter = this.config.filters?.repository;
const topicFilter = this.config.filters?.topic;
const topicIncludesIfMatch = this.config.filters?.topicIncludesIfMatch;
const matchingRepositories = repositories.filter(r => {
const topics: string[] = r.repositoryTopics.nodes.map(
@@ -192,7 +193,8 @@ export class GitHubEntityProvider implements EntityProvider {
return (
!r.isArchived &&
(!repositoryFilter || repositoryFilter.test(r.name)) &&
(!topicFilter || topics.includes(topicFilter)) &&
(!topicFilter ||
(topics.includes(topicFilter) && topicIncludesIfMatch)) &&
r.defaultBranchRef?.name
);
});
@@ -74,13 +74,20 @@ describe('readProviderConfigs', () => {
topic: 'backstage-exclude',
},
},
providerWithTopicFilterAndInclusion: {
organization: 'test-org6',
filters: {
topic: 'backstage-include',
topicIncludesIfMatch: false,
},
},
},
},
},
});
const providerConfigs = readProviderConfigs(config);
expect(providerConfigs).toHaveLength(5);
expect(providerConfigs).toHaveLength(6);
expect(providerConfigs[0]).toEqual({
id: 'providerOrganizationOnly',
organization: 'test-org1',
@@ -88,6 +95,8 @@ describe('readProviderConfigs', () => {
filters: {
repository: undefined,
branch: undefined,
topic: undefined,
topicIncludesIfMatch: false,
},
});
expect(providerConfigs[1]).toEqual({
@@ -97,6 +106,8 @@ describe('readProviderConfigs', () => {
filters: {
repository: undefined,
branch: undefined,
topic: undefined,
topicIncludesIfMatch: false,
},
});
expect(providerConfigs[2]).toEqual({
@@ -106,6 +117,8 @@ describe('readProviderConfigs', () => {
filters: {
repository: /^repository.*filter$/, // repo
branch: undefined, // branch
topic: undefined,
topicIncludesIfMatch: false,
},
});
expect(providerConfigs[3]).toEqual({
@@ -115,6 +128,8 @@ describe('readProviderConfigs', () => {
filters: {
repository: undefined,
branch: 'branch-name',
topic: undefined,
topicIncludesIfMatch: false,
},
});
expect(providerConfigs[4]).toEqual({
@@ -125,6 +140,18 @@ describe('readProviderConfigs', () => {
repository: undefined,
branch: undefined,
topic: 'backstage-exclude',
topicIncludesIfMatch: false,
},
});
expect(providerConfigs[5]).toEqual({
id: 'providerWithTopicFilterAndInclusion',
organization: 'test-org6',
catalogPath: '/catalog-info.yaml',
filters: {
repository: undefined,
branch: undefined,
topic: 'backstage-include',
topicIncludesIfMatch: false,
},
});
});
@@ -18,6 +18,7 @@ import { Config } from '@backstage/config';
const DEFAULT_CATALOG_PATH = '/catalog-info.yaml';
const DEFAULT_PROVIDER_ID = 'default';
const DEFAULT_TOPIC_INCLUSION = false;
export type GitHubEntityProviderConfig = {
id: string;
@@ -27,6 +28,7 @@ export type GitHubEntityProviderConfig = {
repository?: RegExp;
branch?: string;
topic?: string;
topicIncludesIfMatch: boolean;
};
};
@@ -60,6 +62,9 @@ function readProviderConfig(
const repositoryPattern = config.getOptionalString('filters.repository');
const branchPattern = config.getOptionalString('filters.branch');
const topicPattern = config.getOptionalString('filters.topic');
const topicIncludesIfMatch =
config.getOptionalBoolean('filters.topicIncludesIfMatch') ??
DEFAULT_TOPIC_INCLUSION;
return {
id,
@@ -71,6 +76,7 @@ function readProviderConfig(
: undefined,
branch: branchPattern || undefined,
topic: topicPattern || undefined,
topicIncludesIfMatch,
},
};
}