From edfbe81da7b1e2a24d1b80afe7f34395f4c01aa3 Mon Sep 17 00:00:00 2001
From: RahulGautamSingh <rahultesnik@gmail.com>
Date: Wed, 6 Apr 2022 19:58:48 +0530
Subject: [PATCH] refactor: add manager fingerprinting (#14671)

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
Co-authored-by: Rhys Arkins <rhys@arkins.net>
---
 lib/modules/manager/fingerprint.spec.ts | 13 ++++
 lib/modules/manager/index.ts            |  2 +-
 tools/generate-imports.mjs              | 81 +++++++++++++++++++++++++
 3 files changed, 95 insertions(+), 1 deletion(-)
 create mode 100644 lib/modules/manager/fingerprint.spec.ts

diff --git a/lib/modules/manager/fingerprint.spec.ts b/lib/modules/manager/fingerprint.spec.ts
new file mode 100644
index 0000000000..bf29986163
--- /dev/null
+++ b/lib/modules/manager/fingerprint.spec.ts
@@ -0,0 +1,13 @@
+import { regEx } from '../../util/regex';
+import { getManagers, hashMap } from '.';
+
+describe('modules/manager/fingerprint', () => {
+  it('validate manager hash', () => {
+    const regex = regEx(/^[a-f0-9]{64}$/i);
+    const managers = getManagers();
+    for (const [manager] of managers) {
+      const managerHash = hashMap.get(manager);
+      expect(regex.test(managerHash)).toBeTrue();
+    }
+  });
+});
diff --git a/lib/modules/manager/index.ts b/lib/modules/manager/index.ts
index a7f23aac34..1cd65b45a3 100644
--- a/lib/modules/manager/index.ts
+++ b/lib/modules/manager/index.ts
@@ -9,7 +9,7 @@ import type {
   RangeConfig,
   Result,
 } from './types';
-
+export { hashMap } from './fingerprint.generated';
 const managerList = Array.from(managers.keys());
 
 const languageList = Object.values(ProgrammingLanguage);
diff --git a/tools/generate-imports.mjs b/tools/generate-imports.mjs
index 4633a0d09e..bdce7aa37c 100644
--- a/tools/generate-imports.mjs
+++ b/tools/generate-imports.mjs
@@ -1,7 +1,13 @@
+import util from 'util';
 import fs from 'fs-extra';
+import _glob from 'glob';
+import hasha from 'hasha';
+import minimatch from 'minimatch';
 import shell from 'shelljs';
 import upath from 'upath';
 
+const glob = util.promisify(_glob);
+
 shell.echo('generating imports');
 const newFiles = new Set();
 
@@ -32,6 +38,9 @@ const dataPaths = [
   'data',
   'node_modules/emojibase-data/en/shortcodes/github.json',
 ];
+const options = {
+  algorithm: 'sha256',
+};
 
 /**
  *
@@ -63,6 +72,43 @@ function expandPaths(paths) {
     .reduce((x, y) => x.concat(y));
 }
 
+/**
+ * @param {string} filePath
+ * @returns {Promise<string>}
+ */
+async function getFileHash(filePath) {
+  try {
+    const hash = await hasha.fromFile(filePath, options);
+    return hash;
+  } catch (err) {
+    throw new Error(`ERROR: Unable to generate hash for ${filePath}`);
+  }
+}
+
+/**
+ *
+ * @param {string} managerName
+ * @returns {Promise<string>}
+ */
+export async function getManagerHash(managerName) {
+  /** @type {string[]} */
+  let hashes = [];
+  const files = (await glob(`lib/modules/manager/${managerName}/**`)).filter(
+    (fileName) => minimatch(fileName, '*.+(snap|spec.ts)', { matchBase: true })
+  );
+
+  for (const fileAddr of files) {
+    const hash = await getFileHash(fileAddr);
+    hashes.push(hash);
+  }
+
+  if (hashes.length) {
+    return hasha(hashes, options);
+  }
+
+  throw new Error(`Unable to generate hash for manager/${managerName}`);
+}
+
 async function generateData() {
   const files = expandPaths(dataPaths).sort();
 
@@ -93,11 +139,46 @@ async function generateData() {
   );
 }
 
+async function generateHash() {
+  shell.echo('generating hashes');
+  try {
+    const hashMap = `export const hashMap = new Map<string, string>();`;
+    /** @type {string[]} */
+    let hashes = [];
+    // get managers list
+    const managers = (
+      await fs.readdir('lib/modules/manager', { withFileTypes: true })
+    )
+      .filter((file) => file.isDirectory())
+      .map((file) => file.name);
+
+    for (const manager of managers) {
+      const hash = await getManagerHash(manager);
+      hashes.push(hash);
+    }
+
+    //add manager hashes to hashMap {key->manager, value->hash}
+    hashes = (await Promise.all(hashes)).map(
+      (hash, index) => `hashMap.set('${managers[index]}','${hash}');`
+    );
+
+    //write hashMap to fingerprint.generated.ts
+    await updateFile(
+      'lib/modules/manager/fingerprint.generated.ts',
+      [hashMap, hashes.join('\n')].join('\n\n')
+    );
+  } catch (err) {
+    shell.echo('ERROR:', err.message);
+    process.exit(1);
+  }
+}
+
 // eslint-disable-next-line @typescript-eslint/no-floating-promises
 (async () => {
   try {
     // data-files
     await generateData();
+    await generateHash();
     await Promise.all(
       shell
         .find('lib/**/*.generated.ts')
-- 
GitLab