diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md index 61fbb6e6d26669a74ba70f9fa8a7e11e41c8e9b9..32f562c24611cc6144d1aa5627b93ff4e2b8d58c 100644 --- a/docs/usage/self-hosted-configuration.md +++ b/docs/usage/self-hosted-configuration.md @@ -85,11 +85,24 @@ You can limit which repositories Renovate can access by using the `autodiscoverF You can use this option to filter the list of repositories that the Renovate bot account can access through `autodiscover`. It takes a [minimatch](https://www.npmjs.com/package/minimatch) glob-style or regex pattern. +If you set multiple filters, then the matches of each filter are added to the overall result. + +If you use an environment variable or the CLI to set the value for `autodiscoverFilter`, then commas `,` within filters are not supported. +Commas will be used as delimiter for a new filter. + +``` +# DO NOT use commas inside the filter if your are using env or cli variables to configure it. +RENOVATE_AUTODISCOVER_FILTER="/myapp/{readme.md,src/**}" + +# in this example you can use regex instead +RENOVATE_AUTODISCOVER_FILTER="/myapp/(readme\.md|src/.*)/" +``` + **Minimatch**: ```json { - "autodiscoverFilter": "project/*" + "autodiscoverFilter": ["project/*"] } ``` @@ -99,15 +112,17 @@ All text inside the start and end `/` will be treated as a regular expression. ```json { - "autodiscoverFilter": "/project/.*/" + "autodiscoverFilter": ["/project/.*/"] } ``` -You can negate the regex by putting a `!` in front: +You can negate the regex by putting a `!` in front. +Only use a single negation and don't mix with other filters because all filters are combined with `or`. +If using negations, all repositories except those who match the regex are added to the result: ```json { - "autodiscoverFilter": "!/project/.*/" + "autodiscoverFilter": ["!/project/.*/"] } ``` diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index 4056537b36949ab8856bced3e260c45c4f4576d2..0a707078fc4c0ac50ac0ed76ac64aa58f4764ca2 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -723,7 +723,9 @@ const options: RenovateOptions[] = [ name: 'autodiscoverFilter', description: 'Filter the list of autodiscovered repositories.', stage: 'global', - type: 'string', + type: 'array', + subType: 'string', + allowString: true, default: null, globalOnly: true, }, diff --git a/lib/config/types.ts b/lib/config/types.ts index 21f6f50559845df320c265eef15ff41948ee5a25..d7815f369093ad0edda8bac795be1d4ec4b20484 100644 --- a/lib/config/types.ts +++ b/lib/config/types.ts @@ -84,7 +84,7 @@ export interface RenovateSharedConfig { // The below should contain config options where stage=global export interface GlobalOnlyConfig { autodiscover?: boolean; - autodiscoverFilter?: string; + autodiscoverFilter?: string[]; baseDir?: string; cacheDir?: string; containerbaseDir?: string; diff --git a/lib/workers/global/autodiscover.spec.ts b/lib/workers/global/autodiscover.spec.ts index 2f083c7e7a381fce885ef7a70788dbd94e8c9656..c2409d94087d929d3bdd89b4e7777bf557bac688 100644 --- a/lib/workers/global/autodiscover.spec.ts +++ b/lib/workers/global/autodiscover.spec.ts @@ -53,7 +53,7 @@ describe('workers/global/autodiscover', () => { it('filters autodiscovered github repos', async () => { config.autodiscover = true; - config.autodiscoverFilter = 'project/re*'; + config.autodiscoverFilter = ['project/re*']; config.platform = PlatformId.Github; hostRules.find = jest.fn(() => ({ token: 'abc', @@ -67,7 +67,7 @@ describe('workers/global/autodiscover', () => { it('filters autodiscovered github repos but nothing matches', async () => { config.autodiscover = true; - config.autodiscoverFilter = 'project/re*'; + config.autodiscoverFilter = ['project/re*']; config.platform = 'github'; hostRules.find = jest.fn(() => ({ token: 'abc', @@ -81,7 +81,7 @@ describe('workers/global/autodiscover', () => { it('filters autodiscovered github repos with regex', async () => { config.autodiscover = true; - config.autodiscoverFilter = '/project/re*./'; + config.autodiscoverFilter = ['/project/re*./']; config.platform = PlatformId.Github; hostRules.find = jest.fn(() => ({ token: 'abc', @@ -95,7 +95,7 @@ describe('workers/global/autodiscover', () => { it('filters autodiscovered github repos with regex negation', async () => { config.autodiscover = true; - config.autodiscoverFilter = '!/project/re*./'; + config.autodiscoverFilter = ['!/project/re*./']; config.platform = PlatformId.Github; hostRules.find = jest.fn(() => ({ token: 'abc', @@ -109,7 +109,7 @@ describe('workers/global/autodiscover', () => { it('fail if regex pattern is not valid', async () => { config.autodiscover = true; - config.autodiscoverFilter = '/project/re**./'; + config.autodiscoverFilter = ['/project/re**./']; config.platform = PlatformId.Github; hostRules.find = jest.fn(() => ({ token: 'abc', @@ -119,4 +119,22 @@ describe('workers/global/autodiscover', () => { ); await expect(autodiscoverRepositories(config)).rejects.toThrow(); }); + + it('filters autodiscovered github repos with multiple values', async () => { + config.autodiscover = true; + config.autodiscoverFilter = ['another-project/re*', 'department/dev/*']; + config.platform = 'github'; + hostRules.find = jest.fn(() => ({ + token: 'abc', + })); + const expectedRepositories = [ + 'another-project/repo', + 'department/dev/aProject', + ]; + ghApi.getRepos = jest.fn(() => + Promise.resolve(['another-project/another-repo', ...expectedRepositories]) + ); + const res = await autodiscoverRepositories(config); + expect(res.repositories).toEqual(expectedRepositories); + }); }); diff --git a/lib/workers/global/autodiscover.ts b/lib/workers/global/autodiscover.ts index 4dba5d7cf1fac8e3f4c36f7cbb84ba74deca6abe..daad41efe2d9b5ff5b8dbbe52570f52037ab240c 100644 --- a/lib/workers/global/autodiscover.ts +++ b/lib/workers/global/autodiscover.ts @@ -30,20 +30,10 @@ export async function autodiscoverRepositories( ); return config; } + if (config.autodiscoverFilter) { - if (isConfigRegex(config.autodiscoverFilter)) { - const autodiscoveryPred = configRegexPredicate(config.autodiscoverFilter); - if (!autodiscoveryPred) { - throw new Error( - `Failed to parse regex pattern "${config.autodiscoverFilter}"` - ); - } - discovered = discovered.filter(autodiscoveryPred); - } else { - discovered = discovered.filter( - minimatch.filter(config.autodiscoverFilter) - ); - } + discovered = applyFilters(discovered, config.autodiscoverFilter); + if (!discovered.length) { // Soft fail (no error thrown) if no accessible repositories match the filter logger.debug('None of the discovered repositories matched the filter'); @@ -54,6 +44,7 @@ export async function autodiscoverRepositories( { length: discovered.length, repositories: discovered }, `Autodiscovered repositories` ); + // istanbul ignore if if (config.repositories?.length) { logger.debug( @@ -80,3 +71,24 @@ export async function autodiscoverRepositories( } return { ...config, repositories: discovered }; } + +export function applyFilters(repos: string[], filters: string[]): string[] { + const matched = new Set<string>(); + + for (const filter of filters) { + let res: string[]; + if (isConfigRegex(filter)) { + const autodiscoveryPred = configRegexPredicate(filter); + if (!autodiscoveryPred) { + throw new Error(`Failed to parse regex pattern "${filter}"`); + } + res = repos.filter(autodiscoveryPred); + } else { + res = repos.filter(minimatch.filter(filter)); + } + for (const repository of res) { + matched.add(repository); + } + } + return [...matched]; +}