From 2be23bb8692f13ac9ef44e176db299c47edf0e36 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Wed, 6 Sep 2023 07:28:57 +0200
Subject: [PATCH] feat: advanced optimizeForDisabled (#24255)

---
 docs/usage/self-hosted-configuration.md  | 18 ++++++++++---
 lib/workers/repository/init/apis.spec.ts | 34 ++++++++++++++++++++++++
 lib/workers/repository/init/apis.ts      | 23 ++++++++++++++++
 3 files changed, 72 insertions(+), 3 deletions(-)

diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md
index b1147e32aa..e6ff1a0c97 100644
--- a/docs/usage/self-hosted-configuration.md
+++ b/docs/usage/self-hosted-configuration.md
@@ -585,14 +585,26 @@ Similarly to `onboardingBranch`, if you have an existing Renovate installation a
 
 When this option is `true`, Renovate will do the following during repository initialization:
 
-- Try to fetch the default config file (`renovate.json`)
+- Try to fetch the default config file (e.g. `renovate.json`)
 - Check if the file contains `"enabled": false`
+- If so, skip cloning and skip the repository immediately
+
+If `onboardingConfigFileName` is set, that file name will be used instead of the default.
 
 If the file exists and the config is disabled, Renovate will skip the repo without cloning it.
 Otherwise, it will continue as normal.
 
-This option is only useful where the ratio of disabled repos is quite high.
-You spend one extra API call per repo, but skip cloning disabled repositories.
+It can speed up initialization significantly in cases where most repositories are disabled, at the cost of an extra API call for enabled repositories.
+
+A second, advanced, use also exists when the bot global config contains `extends: [":disableRenovate"]`.
+In that case, Renovate will check for a repo config file containing one of the following:
+
+- `extends: [":enableRenovate"]`
+- `ignorePresets: [":disableRenovate"]`
+- `enabled: true`
+
+If any of those three are found, then the repo initialization will continue.
+If not, then Renoate will skip the repository without cloning it.
 
 ## password
 
diff --git a/lib/workers/repository/init/apis.spec.ts b/lib/workers/repository/init/apis.spec.ts
index 07df2ace20..8295f41bd4 100644
--- a/lib/workers/repository/init/apis.spec.ts
+++ b/lib/workers/repository/init/apis.spec.ts
@@ -154,5 +154,39 @@ describe('workers/repository/init/apis', () => {
       expect(workerPlatformConfig.onboardingConfigFileName).toBe('foo.bar');
       expect(platform.getJsonFile).toHaveBeenCalledWith('renovate.json');
     });
+
+    it('checks for re-enablement and continues', async () => {
+      platform.initRepo.mockResolvedValueOnce({
+        defaultBranch: 'master',
+        isFork: false,
+        repoFingerprint: '123',
+      });
+      platform.getJsonFile.mockResolvedValueOnce({
+        enabled: true,
+      });
+      const workerPlatformConfig = await initApis({
+        ...config,
+        optimizeForDisabled: true,
+        extends: [':disableRenovate'],
+      });
+      expect(workerPlatformConfig).toBeTruthy();
+      expect(platform.getJsonFile).toHaveBeenCalledWith('renovate.json');
+    });
+
+    it('checks for re-enablement and skips', async () => {
+      platform.initRepo.mockResolvedValueOnce({
+        defaultBranch: 'master',
+        isFork: false,
+        repoFingerprint: '123',
+      });
+      platform.getJsonFile.mockResolvedValueOnce(null);
+      await expect(
+        initApis({
+          ...config,
+          optimizeForDisabled: true,
+          extends: [':disableRenovate'],
+        })
+      ).rejects.toThrow(REPOSITORY_DISABLED);
+    });
   });
 });
diff --git a/lib/workers/repository/init/apis.ts b/lib/workers/repository/init/apis.ts
index 823f2fdf0f..554e4829de 100644
--- a/lib/workers/repository/init/apis.ts
+++ b/lib/workers/repository/init/apis.ts
@@ -34,6 +34,29 @@ async function validateOptimizeForDisabled(
     if (renovateConfig?.enabled === false) {
       throw new Error(REPOSITORY_DISABLED_BY_CONFIG);
     }
+    /*
+     * The following is to support a use case within Mend customers where:
+     *  - Bot admins configure install the bot into every repo
+     *  - Bot admins configure `extends: [':disableRenovate'] in order to skip repos by default
+     *  - Repo users can push a `renovate.json` containing `extends: [':enableRenovate']` to re-enable Renovate
+     */
+    if (config.extends?.includes(':disableRenovate')) {
+      logger.debug(
+        'Global config disables Renovate - checking renovate.json to see if it is re-enabled'
+      );
+      if (
+        renovateConfig?.extends?.includes(':enableRenovate') ??
+        renovateConfig?.ignorePresets?.includes(':disableRenovate') ??
+        renovateConfig?.enabled
+      ) {
+        logger.debug('Repository config re-enables Renovate - continuing');
+      } else {
+        logger.debug(
+          'Repository config does not re-enable Renovate - skipping'
+        );
+        throw new Error(REPOSITORY_DISABLED_BY_CONFIG);
+      }
+    }
   }
 }
 
-- 
GitLab