From bbaaadc0f7fada982facd297e6fdba230e18bc76 Mon Sep 17 00:00:00 2001 From: Niel Markwick Date: Thu, 5 Dec 2024 16:18:22 +0100 Subject: [PATCH] chore: add eslint typescript config and fix eslint issues raised. --- cloudrun-malware-scanner/Dockerfile | 4 +- cloudrun-malware-scanner/config.ts | 82 ++- cloudrun-malware-scanner/eslint.config.mjs | 27 +- cloudrun-malware-scanner/gcs-proxy-server.ts | 9 +- cloudrun-malware-scanner/metrics.ts | 5 +- cloudrun-malware-scanner/package-lock.json | 494 +++++++++++++++++- cloudrun-malware-scanner/package.json | 7 +- cloudrun-malware-scanner/scanner.ts | 10 +- cloudrun-malware-scanner/server.ts | 56 +- cloudrun-malware-scanner/spec/config.spec.ts | 38 +- cloudrun-malware-scanner/spec/scanner.spec.ts | 85 +-- cloudrun-malware-scanner/tsconfig.json | 3 +- 12 files changed, 669 insertions(+), 151 deletions(-) diff --git a/cloudrun-malware-scanner/Dockerfile b/cloudrun-malware-scanner/Dockerfile index 8c8117d..2b1ca5e 100644 --- a/cloudrun-malware-scanner/Dockerfile +++ b/cloudrun-malware-scanner/Dockerfile @@ -96,7 +96,7 @@ RUN set -x \ WORKDIR /app COPY . /app -# Install required NPM modules and build -RUN npm install && npm run build +# Install NPM modules, build, then remove dev modules +RUN npm ci && npm run build && npm ci --omit=dev CMD ["bash", "bootstrap.sh"] diff --git a/cloudrun-malware-scanner/config.ts b/cloudrun-malware-scanner/config.ts index 604f3ea..87a368a 100644 --- a/cloudrun-malware-scanner/config.ts +++ b/cloudrun-malware-scanner/config.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import {logger} from './logger'; import {readFileSync} from 'node:fs'; import {Storage} from '@google-cloud/storage'; @@ -46,11 +45,6 @@ const BUCKET_TYPES = ['unscanned', 'clean', 'quarantined'] as Array< /** * Read configuration from JSON configuration file, parse, verify * and return a Config object - * - * @async - * @param {string} configFile - * @param {Storage} storage - * @return {Promise} */ export async function readAndVerifyConfig( configFile: string, @@ -60,50 +54,36 @@ export async function readAndVerifyConfig( let configText; try { configText = readFileSync(configFile, {encoding: 'utf-8'}); - } catch (e: any) { + } catch (e) { logger.fatal( e, - `Unable to read JSON file from ${configFile}: ${e.message}`, + `Unable to read JSON file from ${configFile}: ${e as Error}`, ); throw e; } try { - return validateConfig(parseConfig(configText), storage); - } catch (e: any) { - logger.fatal(e, `Failed parsing config file: ${configFile}: ${e.message}`); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const config = JSON.parse(configText); + if (typeof config !== 'object') { + throw new Error('config must be an object'); + } + return await validateConfig(config, storage); + } catch (e) { + logger.fatal(e, `Failed parsing config file: ${configFile}: ${e as Error}`); throw e; } } +// Allow any for this function, as it is validating a parsed object. +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /** - * @param {string} configText - * @returns {Config} - */ -function parseConfig(configText: string): Config { - /** @type {Config} */ - let config; - - try { - config = JSON.parse(configText); - } catch (e: any) { - throw new Error(`Failed to parse configuration as JSON: ${e}`); - } - return config; -} - -/** - * Read configuration from JSON configuration file, verify - * and return a Config object - * - * @async - * @param {any} config - * @param {Storage} storage - * @return {Promise} + * Validate and freeze the Config object. */ async function validateConfig(config: any, storage: Storage): Promise { delete config.comments; - if (config.buckets.length === 0) { + if (config.buckets == null || config.buckets.length === 0) { logger.fatal(`No buckets configured for scanning`); throw new Error('No buckets configured'); } @@ -113,12 +93,12 @@ async function validateConfig(config: any, storage: Storage): Promise { // Check buckets are specified and exist. let success = true; for (let x = 0; x < config.buckets.length; x++) { - const bucketDefs = config.buckets[x]; + const bucketDefs = config.buckets[x] as BucketDefs; for (const bucketType of BUCKET_TYPES) { if ( !(await checkBucketExists( bucketDefs[bucketType], - `config.buckets[${x}].${bucketType}`, + `config.buckets[${x.toString()}].${bucketType}`, storage, )) ) { @@ -130,13 +110,15 @@ async function validateConfig(config: any, storage: Storage): Promise { bucketDefs.unscanned === bucketDefs.quarantined || bucketDefs.clean === bucketDefs.quarantined ) { - logger.fatal(`Config Error: buckets[${x}]: bucket names are not unique`); + logger.fatal( + `Config Error: buckets[${x.toString()}]: bucket names are not unique`, + ); success = false; } } if ( !(await checkBucketExists( - config.ClamCvdMirrorBucket, + config.ClamCvdMirrorBucket as string, 'ClamCvdMirrorBucket', storage, )) @@ -165,7 +147,7 @@ async function validateConfig(config: any, storage: Storage): Promise { } else { // config.fileExclusionPatterns is an array, check each value and // convert to a regexp in fileExclusionRegexps[] - for (const i in config.fileExclusionPatterns) { + for (let i = 0; i < config.fileExclusionPatterns.length; i++) { let pattern: string | undefined; let flags: string | undefined; @@ -173,7 +155,9 @@ async function validateConfig(config: any, storage: Storage): Promise { // "^.*\\.tmp$" // or an array with pattern and flags, eg for case-insensive matching: // [ "^.*\\tmp$", "i" ] - const element = config.fileExclusionPatterns[i]; + const element = config.fileExclusionPatterns[i] as + | string + | Array; if (typeof element === 'string') { // validate regex as simple string pattern = element; @@ -198,10 +182,10 @@ async function validateConfig(config: any, storage: Storage): Promise { } else { try { config.fileExclusionRegexps[i] = new RegExp(pattern, flags); - } catch (e: any) { + } catch (e) { logger.fatal( e, - `Config Error: fileExclusionPatterns[${i}]: Regexp compile failed for ${JSON.stringify(config.fileExclusionPatterns[i])}: ${e.message}`, + `Config Error: fileExclusionPatterns[${i}]: Regexp compile failed for ${JSON.stringify(config.fileExclusionPatterns[i])}: ${e as Error}`, ); success = false; } @@ -215,16 +199,12 @@ async function validateConfig(config: any, storage: Storage): Promise { throw new Error('Invalid configuration'); } - return Object.freeze(config); + return Object.freeze(config as Config); } +/* eslint-enable */ /** * Check that given bucket exists. Returns true on success - * - * @param {string} bucketName - * @param {string} configName - * @param {Storage} storage - * @return {Promise} */ async function checkBucketExists( bucketName: string, @@ -244,9 +224,9 @@ async function checkBucketExists( .bucket(bucketName) .getFiles({maxResults: 1, prefix: 'zzz', autoPaginate: false}); return true; - } catch (e: any) { + } catch (e) { logger.fatal( - `Error in config: cannot view files in "${configName}" : ${bucketName} : ${e}`, + `Error in config: cannot view files in "${configName}" : ${bucketName} : ${e as Error}`, ); logger.debug({err: e}); return false; diff --git a/cloudrun-malware-scanner/eslint.config.mjs b/cloudrun-malware-scanner/eslint.config.mjs index 924560a..c313b50 100644 --- a/cloudrun-malware-scanner/eslint.config.mjs +++ b/cloudrun-malware-scanner/eslint.config.mjs @@ -1,17 +1,26 @@ +// @ts-check import globals from "globals"; -import pluginJs from "@eslint/js"; +import tseslint from "typescript-eslint"; -export default [ +export default tseslint.config( { - files: ["*.js"], - languageOptions: { sourceType: "commonjs", globals: globals.node }, + ignores: [ + "*.d.ts", + "eslint.config.mjs", + "*.js", + "node_modules/**", + "build/**", + "pyenv/**", + ], }, { - files: ["spec/*.js"], languageOptions: { - sourceType: "commonjs", - globals: [globals.jasmine, globals.node], + parserOptions: { + project: "./tsconfig.json", + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, }, }, - pluginJs.configs.recommended, -]; + tseslint.configs.recommendedTypeChecked, +); diff --git a/cloudrun-malware-scanner/gcs-proxy-server.ts b/cloudrun-malware-scanner/gcs-proxy-server.ts index 54d15c8..56b850d 100644 --- a/cloudrun-malware-scanner/gcs-proxy-server.ts +++ b/cloudrun-malware-scanner/gcs-proxy-server.ts @@ -17,7 +17,7 @@ import * as process from 'node:process'; import {GoogleAuth} from 'google-auth-library'; import {logger} from './logger.js'; -import {readAndVerifyConfig, Config} from './config.js'; +import {readAndVerifyConfig} from './config.js'; import * as httpProxy from 'http-proxy'; import {Storage} from '@google-cloud/storage'; import {name as packageName, version as packageVersion} from './package.json'; @@ -34,7 +34,7 @@ let accessTokenRefreshTimeout: NodeJS.Timeout | null = null; let clamCvdMirrorBucket = 'uninitialized'; -async function accessTokenRefresh(): Promise { +async function accessTokenRefresh() { if (accessTokenRefreshTimeout) { clearTimeout(accessTokenRefreshTimeout); accessTokenRefreshTimeout = null; @@ -60,6 +60,7 @@ async function accessTokenRefresh(): Promise { `Next access token refresh check at ${nextCheckDate.toISOString()}`, ); accessTokenRefreshTimeout = setTimeout( + // eslint-disable-next-line @typescript-eslint/no-misused-promises accessTokenRefresh, nextCheckDate.getTime() - new Date().getTime(), ); @@ -98,7 +99,7 @@ function handleProxyReq( } } -async function setupGcsReverseProxy(): Promise { +function setupGcsReverseProxy() { const proxy = httpProxy.createProxyServer({ target: 'https://storage.googleapis.com/', changeOrigin: true, @@ -135,7 +136,7 @@ async function run(): Promise { clamCvdMirrorBucket = config.ClamCvdMirrorBucket; await accessTokenRefresh(); - await setupGcsReverseProxy(); + setupGcsReverseProxy(); } // Start the service, exiting on error. diff --git a/cloudrun-malware-scanner/metrics.ts b/cloudrun-malware-scanner/metrics.ts index 02f4fe8..8bb229d 100644 --- a/cloudrun-malware-scanner/metrics.ts +++ b/cloudrun-malware-scanner/metrics.ts @@ -86,7 +86,7 @@ class DiagToPinoLogger implements OpenTelemetryApi.DiagLogger { // them. this.suppressErrors = false; } - + /* eslint-disable @typescript-eslint/no-explicit-any */ verbose(message: string, ...args: any[]) { logger.trace('otel: ' + message, args); } @@ -108,6 +108,7 @@ class DiagToPinoLogger implements OpenTelemetryApi.DiagLogger { logger.error('otel: ' + message, args); } } + /* eslint-enable */ } OpenTelemetryApi.default.diag.setLogger(new DiagToPinoLogger(), { @@ -234,7 +235,7 @@ function writeCvdMirrorUpdatedMetric(success: boolean, isUpdated: boolean) { }); } -async function initMetrics(projectId: string) { +function initMetrics(projectId: string) { if (!projectId) { throw Error('Unable to proceed without a Project ID'); } diff --git a/cloudrun-malware-scanner/package-lock.json b/cloudrun-malware-scanner/package-lock.json index 3c76379..c7cf469 100644 --- a/cloudrun-malware-scanner/package-lock.json +++ b/cloudrun-malware-scanner/package-lock.json @@ -35,7 +35,8 @@ "jasmine": "^5.5.0", "jasmine-console-reporter": "^3.1.0", "prettier": "^3.4.2", - "typescript": "^5.7.2" + "typescript": "^5.7.2", + "typescript-eslint": "^8.17.0" } }, "node_modules/@babel/code-frame": { @@ -918,6 +919,44 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -1231,6 +1270,230 @@ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz", + "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/type-utils": "8.17.0", + "@typescript-eslint/utils": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz", + "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/typescript-estree": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", + "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz", + "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.17.0", + "@typescript-eslint/utils": "8.17.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", + "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", + "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/visitor-keys": "8.17.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", + "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.17.0", + "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/typescript-estree": "8.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", + "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.17.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -1452,6 +1715,19 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -2345,6 +2621,36 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -2393,6 +2699,16 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -2405,6 +2721,19 @@ "node": ">=16.0.0" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -2831,6 +3160,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, "node_modules/gtoken": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", @@ -3114,6 +3450,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -3580,6 +3926,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -3588,6 +3944,20 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", @@ -4036,6 +4406,19 @@ "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", "dev": true }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pino": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/pino/-/pino-9.5.0.tgz", @@ -4196,6 +4579,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -4319,6 +4723,41 @@ "node": ">=14" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4731,6 +5170,19 @@ "dev": true, "license": "MIT" }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -4744,6 +5196,19 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4782,6 +5247,33 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.17.0.tgz", + "integrity": "sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.17.0", + "@typescript-eslint/parser": "8.17.0", + "@typescript-eslint/utils": "8.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/cloudrun-malware-scanner/package.json b/cloudrun-malware-scanner/package.json index 31e6908..ee8deb9 100644 --- a/cloudrun-malware-scanner/package.json +++ b/cloudrun-malware-scanner/package.json @@ -14,8 +14,8 @@ "start-proxy": "node --enable-source-maps build/gcs-proxy-server.js", "test": "env NODE_ENV=test NODE_OPTIONS=--enable-source-maps jasmine", "test:fancy": "env NODE_ENV=test jasmine --reporter=jasmine-console-reporter", - "eslint": "eslint *.js", - "eslint-fix": "eslint --fix *.js", + "eslint": "eslint", + "eslint-fix": "eslint --fix", "prepare": "{ git rev-parse --is-inside-work-tree >/dev/null 2>/dev/null && test \"$NODE_ENV\" != production -a \"$CI\" != true && cd .. && husky cloudrun-malware-scanner/.husky ; } || echo 'skipping husky setup'", "build": "tsc --build", "pretest": "npm run build" @@ -49,6 +49,7 @@ "jasmine": "^5.5.0", "jasmine-console-reporter": "^3.1.0", "prettier": "^3.4.2", - "typescript": "^5.7.2" + "typescript": "^5.7.2", + "typescript-eslint": "^8.17.0" } } diff --git a/cloudrun-malware-scanner/scanner.ts b/cloudrun-malware-scanner/scanner.ts index ed66547..1a71676 100644 --- a/cloudrun-malware-scanner/scanner.ts +++ b/cloudrun-malware-scanner/scanner.ts @@ -121,8 +121,8 @@ export class Scanner { `Request has bucket name ${storageObject.bucket} which is not an unscanned bucket in config`, ); } - } catch (e: any) { - logger.error(`Ignoring request: ${e}`); + } catch (e) { + logger.error(`Ignoring request: ${e as Error}`); this.metricsClient.writeScanFailed(); return {message: 'ignoring invalid request', status: 'ignored'}; } @@ -258,14 +258,14 @@ export class Scanner { clam_version: clamdVersion, }; } - } catch (e: any) { + } catch (e) { logger.error( {err: e}, - `Exception when processing gs://${storageObject.bucket}/${storageObject.name}: ${e}`, + `Exception when processing gs://${storageObject.bucket}/${storageObject.name}: ${e as Error}`, ); // Check for an API error code - const errcode = e.code as number | undefined; + const errcode = (e as gcs.ApiError).code; if (errcode && [403, 404].includes(errcode)) { // Permission denied/file not found can be raised by the stream reading // and by the object move. They cannot be retried, so respond diff --git a/cloudrun-malware-scanner/server.ts b/cloudrun-malware-scanner/server.ts index 43addb6..af74128 100644 --- a/cloudrun-malware-scanner/server.ts +++ b/cloudrun-malware-scanner/server.ts @@ -28,7 +28,7 @@ import { import * as metrics from './metrics'; import {Scanner, StorageObjectData} from './scanner'; import {promisify} from 'node:util'; -import {execFile} from 'node:child_process'; +import {execFile, ExecFileException} from 'node:child_process'; import {setTimeout} from 'timers/promises'; import {readAndVerifyConfig, Config} from './config'; @@ -36,18 +36,13 @@ const execFilePromise = promisify(execFile); /** Encapsulates the HTTP server and its methods */ class Server { - scanner: Scanner; - config: Config; - port: number; app: express.Application; - /** - * @param {Scanner} scanner - * @param {Config} config - * @param {number} port - */ - constructor(scanner: Scanner, config: Config, port: number) { - this.scanner = scanner; + constructor( + private readonly scanner: Scanner, + private readonly config: Config, + private readonly port: number, + ) { this.config = config; this.port = port; @@ -81,8 +76,8 @@ class Server { try { await this.scanner.pingClamD(); res.status(200).json({message: 'Health Check Suceeded'}); - } catch (e: any) { - logger.fatal(e, `Health check failed to contact clamd: ${e.message}`); + } catch (e) { + logger.fatal(e, `Health check failed to contact clamd: ${e as Error}`); res.status(500).json({message: 'Health Check Failed', status: 'error'}); } } @@ -93,9 +88,13 @@ class Server { */ async handlePost(req: express.Request, res: express.Response): Promise { try { - switch (req.body.kind) { + // eslint-disable-next-line + const objectKind = req.body?.kind as string | null; + switch (objectKind) { case 'storage#object': - res.json(await this.scanner.handleGcsObject(req.body)); + res.json( + await this.scanner.handleGcsObject(req.body as StorageObjectData), + ); break; case 'schedule#cvd_update': @@ -104,16 +103,16 @@ class Server { default: logger.error( - {payload: req.body}, - `Error processing request: object kind: ${req.body.kind} is not supported`, + {payload: req.body as unknown}, + `Error processing request: object kind: ${objectKind} is not supported`, ); res.status(400).json({message: 'invalid request', status: 'error'}); break; } - } catch (e: any) { + } catch (e) { logger.error( - {err: e, payload: req.body}, - `Failure when processing request: ${e}`, + {err: e, payload: req.body as unknown}, + `Failure when processing request: ${e as Error}`, ); res.status(500).json({message: e, status: 'error'}); } @@ -130,9 +129,6 @@ class Server { /** * Triggers a update check on the CVD Mirror GCS bucket. - * - * @param {Config} config - * @returns {Promise} */ async function handleCvdUpdate(config: Config): Promise<{ status: string; @@ -159,19 +155,19 @@ async function handleCvdUpdate(config: Config): Promise<{ status: 'CvdUpdateComplete', updated: isUpdated, }; - } catch (err: any) { + } catch (e) { + const err = e as ExecFileException; logger.error( {err}, - `Failure when running ./updateCvdMirror.sh: ${err}\nstdout: ${err.stdout}\nstderr: \n${err.stderr}`, + `Failure when running ./updateCvdMirror.sh: ${err.message}\nstdout: ${err.stdout}\nstderr: \n${err.stderr}`, ); metrics.writeCvdMirrorUpdated(false, false); - throw err; + throw err as Error; } } /** * Wait up to 5 mins for ClamD to respond - * @param {Scanner} scanner */ async function waitForClamD(scanner: Scanner): Promise { const timeoutMins = 10; @@ -183,8 +179,8 @@ async function waitForClamD(scanner: Scanner): Promise { const version = await scanner.getClamVersion(); logger.info(`Clamd started with version ${version}`); return; - } catch (e: any) { - logger.warn(`Waiting for clamd to start: ${e}`); + } catch (e) { + logger.warn(`Waiting for clamd to start: ${e as Error}`); } await setTimeout(10000); } @@ -203,7 +199,7 @@ async function run(): Promise { // Metrics needs project ID, so get it from GoogleAuth projectId = await new GoogleAuth().getProjectId(); } - await metrics.init(projectId); + metrics.init(projectId); const storage = new Storage({ userAgent: `cloud-solutions/${packageName}-usage-v${packageVersion}`, diff --git a/cloudrun-malware-scanner/spec/config.spec.ts b/cloudrun-malware-scanner/spec/config.spec.ts index e7faa4c..dc374b0 100644 --- a/cloudrun-malware-scanner/spec/config.spec.ts +++ b/cloudrun-malware-scanner/spec/config.spec.ts @@ -35,9 +35,13 @@ describe('Config', () => { let existingBucket: jasmine.SpyObj; beforeEach(() => { - storageServiceSpy = jasmine.createSpyObj('Storage', ['bucket']); + storageServiceSpy = jasmine.createSpyObj('Storage', [ + 'bucket', + ]); - existingBucket = jasmine.createSpyObj('existingBucket', ['getFiles']); + existingBucket = jasmine.createSpyObj('existingBucket', [ + 'getFiles', + ]); (existingBucket.getFiles as jasmine.Spy).and.resolveTo([]); [ ...Object.values(GOOD_CONFIG.buckets[0]), @@ -56,7 +60,7 @@ describe('Config', () => { 'spec/support/bad-config.notjson', storageServiceSpy, ), - ).toBeRejectedWithError(/SyntaxError/); + ).toBeRejectedWithError(SyntaxError, /Expected property/); }); it('throws when a missing config file is passed', async () => { @@ -74,7 +78,8 @@ describe('Config', () => { storageServiceSpy, ); expect(config).toEqual(GOOD_CONFIG); - expect(existingBucket.getFiles).toHaveBeenCalledTimes(4); + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(existingBucket.getFiles as jasmine.Spy).toHaveBeenCalledTimes(4); }); }); @@ -84,11 +89,15 @@ describe('Config', () => { .withArgs('existingBucket') .and.returnValue(existingBucket); - const nonExistingBucket = jasmine.createSpyObj('bucket', ['getFiles']); + const nonExistingBucket = jasmine.createSpyObj('bucket', [ + 'getFiles', + ]); (storageServiceSpy.bucket as jasmine.Spy) .withArgs('nonExistingBucket') .and.returnValue(nonExistingBucket); - nonExistingBucket.getFiles.and.rejectWith(new Error('Bucket not found')); + (nonExistingBucket.getFiles as jasmine.Spy).and.rejectWith( + new Error('Bucket not found'), + ); }); it('returns false when a bucket does not exist', async () => { @@ -111,9 +120,14 @@ describe('Config', () => { }); }); + // Working with 'any' to validate the config validator! + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ + /* eslint-disable @typescript-eslint/no-explicit-any */ + /* eslint-disable @typescript-eslint/no-unsafe-assignment */ describe('validateConfig', () => { it('comments are removed', async () => { const config = structuredClone(GOOD_CONFIG); + (config as any).comments = ['HELLO WORLD']; expect( await Config.TEST_ONLY.validateConfig(config, storageServiceSpy), @@ -136,7 +150,9 @@ describe('Config', () => { (storageServiceSpy.bucket as jasmine.Spy) .withArgs('nonExistingBucket') .and.returnValue(nonExistingBucket); - nonExistingBucket.getFiles.and.rejectWith(new Error('Bucket not found')); + (nonExistingBucket.getFiles as jasmine.Spy).and.rejectWith( + new Error('Bucket not found'), + ); const config = structuredClone(GOOD_CONFIG); @@ -262,12 +278,12 @@ describe('Config', () => { storageServiceSpy, ); expect(validatedConfig.fileExclusionPatterns).toBeUndefined(); - expect(validatedConfig.fileExclusionRegexps!.length).toEqual(3); - expect(validatedConfig.fileExclusionRegexps![0]).toEqual(/simple.*regex/); - expect(validatedConfig.fileExclusionRegexps![1]).toEqual( + expect(validatedConfig.fileExclusionRegexps.length).toEqual(3); + expect(validatedConfig.fileExclusionRegexps[0]).toEqual(/simple.*regex/); + expect(validatedConfig.fileExclusionRegexps[1]).toEqual( /case-insensitve.regex$/i, ); - expect(validatedConfig.fileExclusionRegexps![2]).toEqual( + expect(validatedConfig.fileExclusionRegexps[2]).toEqual( /^[a-z0-9]\.tmp$/, ); }); diff --git a/cloudrun-malware-scanner/spec/scanner.spec.ts b/cloudrun-malware-scanner/spec/scanner.spec.ts index 1921838..22e45e9 100644 --- a/cloudrun-malware-scanner/spec/scanner.spec.ts +++ b/cloudrun-malware-scanner/spec/scanner.spec.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import {Scanner} from '../scanner'; +import {Scanner, StorageObjectData} from '../scanner'; import * as metrics from '../metrics'; -import {Config, BucketDefs} from '../config.js'; +import {Config} from '../config.js'; import {Readable} from 'node:stream'; import * as gcs from '@google-cloud/storage'; import * as ClamdClient from 'clamdjs'; @@ -50,7 +50,7 @@ describe('Scanner', () => { let scanner: Scanner; beforeEach(() => { - clamdClient = jasmine.createSpyObj('ClamdClient', [ + clamdClient = jasmine.createSpyObj('ClamdClient', [ 'createScanner', 'version', 'ping', @@ -62,25 +62,40 @@ describe('Scanner', () => { }); clamdClient.version.and.resolveTo('clamd_version_string\x00'); - metricsClient = jasmine.createSpyObj('MetricsClient', Object.keys(metrics)); + metricsClient = jasmine.createSpyObj('MetricsClient', [ + 'writeScanFailed', + 'writeScanClean', + 'writeScanIgnored', + 'writeScanInfected', + 'writeCvdMirrorUpdated', + 'init', + ]); - storageClient = jasmine.createSpyObj('Storage', ['bucket']); + storageClient = jasmine.createSpyObj('Storage', ['bucket']); - mockUnscannedBucket = jasmine.createSpyObj('unscannedBucket', ['file'], { - name: 'unscannedBucket', - }); + mockUnscannedBucket = jasmine.createSpyObj( + 'unscannedBucket', + ['file'], + { + name: 'unscannedBucket', + }, + ); storageClient.bucket .withArgs('unscannedBucket') .and.returnValue(mockUnscannedBucket); - mockCleanBucket = jasmine.createSpyObj('cleanBucket', ['file'], { - name: 'cleanBucket', - }); + mockCleanBucket = jasmine.createSpyObj( + 'cleanBucket', + ['file'], + { + name: 'cleanBucket', + }, + ); storageClient.bucket .withArgs('cleanBucket') .and.returnValue(mockCleanBucket); - mockQuarantinedBucket = jasmine.createSpyObj( + mockQuarantinedBucket = jasmine.createSpyObj( 'quarantinedBucket', ['file'], {name: 'quarantinedBucket'}, @@ -89,7 +104,7 @@ describe('Scanner', () => { .withArgs('quarantinedBucket') .and.returnValue(mockQuarantinedBucket); - mockFile = jasmine.createSpyObj( + mockFile = jasmine.createSpyObj( 'testFile', ['exists', 'getMetadata', 'createReadStream', 'move'], { @@ -111,18 +126,15 @@ describe('Scanner', () => { }); it('successful pings return', async () => { - // @ts-ignore - incorrect ClamdClient.ping() type clamdClient.ping.and.resolveTo(true); await expectAsync(scanner.pingClamD()).toBeResolved(); }); it('unsuccessful pings throw', async () => { - // @ts-ignore - incorrect ClamdClient.ping() type clamdClient.ping.and.resolveTo(false); await expectAsync(scanner.pingClamD()).toBeRejectedWithError( /clamd PING failed/, ); - // @ts-ignore - incorrect ClamdClient.ping() type clamdClient.ping.and.throwError('exception in Ping'); await expectAsync(scanner.pingClamD()).toBeRejectedWithError( /exception in Ping/, @@ -133,26 +145,30 @@ describe('Scanner', () => { it('validates input', async () => { const response = {message: 'ignoring invalid request', status: 'ignored'}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any let request: any = null; - await expectAsync(scanner.handleGcsObject(request)).toBeResolvedTo( - response, - ); + await expectAsync( + scanner.handleGcsObject(request as StorageObjectData), + ).toBeResolvedTo(response); request = {}; - await expectAsync(scanner.handleGcsObject(request)).toBeResolvedTo( - response, - ); + await expectAsync( + scanner.handleGcsObject(request as StorageObjectData), + ).toBeResolvedTo(response); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access request.name = 'filename'; - await expectAsync(scanner.handleGcsObject(request)).toBeResolvedTo( - response, - ); + await expectAsync( + scanner.handleGcsObject(request as StorageObjectData), + ).toBeResolvedTo(response); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access request.bucket = 'size'; - await expectAsync(scanner.handleGcsObject(request)).toBeResolvedTo( - response, - ); + await expectAsync( + scanner.handleGcsObject(request as StorageObjectData), + ).toBeResolvedTo(response); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access request.bucket = 'invalid_bucket'; - await expectAsync(scanner.handleGcsObject(request)).toBeResolvedTo( - response, - ); + await expectAsync( + scanner.handleGcsObject(request as StorageObjectData), + ).toBeResolvedTo(response); expect(metricsClient.writeScanFailed).toHaveBeenCalledTimes(5); }); @@ -279,7 +295,9 @@ describe('Scanner', () => { (mockFile.exists as jasmine.Spy).and.resolveTo([true]); (mockFile.getMetadata as jasmine.Spy).and.resolveTo([{size: 100}]); - mockReadStream = jasmine.createSpyObj('readableStream', ['destroy']); + mockReadStream = jasmine.createSpyObj('readableStream', [ + 'destroy', + ]); mockFile.createReadStream.and.returnValue(mockReadStream); jasmine.clock().install(); @@ -309,6 +327,7 @@ describe('Scanner', () => { expect(metricsClient.writeScanFailed).toHaveBeenCalledWith( CONFIG.buckets[0].unscanned, ); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(mockReadStream.destroy).toHaveBeenCalled(); }); @@ -323,6 +342,7 @@ describe('Scanner', () => { message: 'scan_success', }); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(mockReadStream.destroy).toHaveBeenCalled(); // .toHaveBeenCalledWith does not work due to overloading of func. expect(mockFile.move.calls.count()).toBe(1); @@ -347,6 +367,7 @@ describe('Scanner', () => { clam_version: CLAMD_VERSION_STRING, }); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(mockReadStream.destroy).toHaveBeenCalled(); // .toHaveBeenCalledWith does not work due to overloading of func. expect(mockFile.move.calls.count()).toBe(1); diff --git a/cloudrun-malware-scanner/tsconfig.json b/cloudrun-malware-scanner/tsconfig.json index 0b749f1..0495623 100644 --- a/cloudrun-malware-scanner/tsconfig.json +++ b/cloudrun-malware-scanner/tsconfig.json @@ -16,7 +16,8 @@ "sourceMap": true, "strict": true, "target": "ES2020", - "resolveJsonModule": true + "resolveJsonModule": true, + "useUnknownInCatchVariables": true }, "include": ["*.ts", "spec/*.ts"], "exclude": ["node_modules"]