From e406cd1131319f4e61b9de1820b5aaa43ace2df8 Mon Sep 17 00:00:00 2001
From: Sebastian Poxhofer <secustor@users.noreply.github.com>
Date: Tue, 18 Jan 2022 21:23:35 +0100
Subject: [PATCH] feat(autodiscover): allow usage of regex patterns (#13243)

Co-authored-by: Rhys Arkins <rhys@arkins.net>
Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>
---
 docs/usage/self-hosted-configuration.md | 22 ++++++++++++--
 lib/workers/global/autodiscover.spec.ts | 38 +++++++++++++++++++++++++
 lib/workers/global/autodiscover.ts      | 15 +++++++++-
 3 files changed, 72 insertions(+), 3 deletions(-)

diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md
index b1b8a67bd3..04eb5a9ac6 100644
--- a/docs/usage/self-hosted-configuration.md
+++ b/docs/usage/self-hosted-configuration.md
@@ -79,9 +79,9 @@ If you want Renovate to run on only a subset of those, use the `autodiscoverFilt
 ## autodiscoverFilter
 
 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 pattern.
+It takes a [minimatch](https://www.npmjs.com/package/minimatch) glob-style or regex pattern.
 
-e.g.
+**Minimatch**:
 
 ```json
 {
@@ -89,6 +89,24 @@ e.g.
 }
 ```
 
+**Regex**:
+
+All text inside the start and end `/` will be treated as a regular expression.
+
+```json
+{
+  "autodiscoverFilter": "/project/.*/"
+}
+```
+
+You can negate the regex by putting a `!` in front:
+
+```json
+{
+  "autodiscoverFilter": "!/project/.*/"
+}
+```
+
 ## baseDir
 
 By default Renovate uses a temporary directory like `/tmp/renovate` to store its data.
diff --git a/lib/workers/global/autodiscover.spec.ts b/lib/workers/global/autodiscover.spec.ts
index 076d179cc3..deaf9af9f8 100644
--- a/lib/workers/global/autodiscover.spec.ts
+++ b/lib/workers/global/autodiscover.spec.ts
@@ -72,4 +72,42 @@ describe('workers/global/autodiscover', () => {
     const res = await autodiscoverRepositories(config);
     expect(res).toEqual(config);
   });
+  it('filters autodiscovered github repos with regex', async () => {
+    config.autodiscover = true;
+    config.autodiscoverFilter = '/project/re*./';
+    config.platform = PlatformId.Github;
+    hostRules.find = jest.fn(() => ({
+      token: 'abc',
+    }));
+    ghApi.getRepos = jest.fn(() =>
+      Promise.resolve(['project/repo', 'project/another-repo'])
+    );
+    const res = await autodiscoverRepositories(config);
+    expect(res.repositories).toEqual(['project/repo']);
+  });
+  it('filters autodiscovered github repos with regex negation', async () => {
+    config.autodiscover = true;
+    config.autodiscoverFilter = '!/project/re*./';
+    config.platform = PlatformId.Github;
+    hostRules.find = jest.fn(() => ({
+      token: 'abc',
+    }));
+    ghApi.getRepos = jest.fn(() =>
+      Promise.resolve(['project/repo', 'project/another-repo'])
+    );
+    const res = await autodiscoverRepositories(config);
+    expect(res.repositories).toEqual(['project/another-repo']);
+  });
+  it('fail if regex pattern is not valid', async () => {
+    config.autodiscover = true;
+    config.autodiscoverFilter = '/project/re**./';
+    config.platform = PlatformId.Github;
+    hostRules.find = jest.fn(() => ({
+      token: 'abc',
+    }));
+    ghApi.getRepos = jest.fn(() =>
+      Promise.resolve(['project/repo', 'project/another-repo'])
+    );
+    await expect(autodiscoverRepositories(config)).rejects.toThrow();
+  });
 });
diff --git a/lib/workers/global/autodiscover.ts b/lib/workers/global/autodiscover.ts
index 03b66b293a..3a4c7747c6 100644
--- a/lib/workers/global/autodiscover.ts
+++ b/lib/workers/global/autodiscover.ts
@@ -3,6 +3,7 @@ import minimatch from 'minimatch';
 import type { AllConfig } from '../../config/types';
 import { logger } from '../../logger';
 import { platform } from '../../platform';
+import { configRegexPredicate, isConfigRegex } from '../../util/regex';
 
 // istanbul ignore next
 function repoName(value: string | { repository: string }): string {
@@ -30,7 +31,19 @@ export async function autodiscoverRepositories(
     return config;
   }
   if (config.autodiscoverFilter) {
-    discovered = discovered.filter(minimatch.filter(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)
+      );
+    }
     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');
-- 
GitLab