Skip to content

Commit

Permalink
chore: add eslint typescript config
Browse files Browse the repository at this point in the history
and fix eslint issues raised.
  • Loading branch information
nielm committed Dec 5, 2024
1 parent 06ea4b0 commit bbaaadc
Show file tree
Hide file tree
Showing 12 changed files with 669 additions and 151 deletions.
4 changes: 2 additions & 2 deletions cloudrun-malware-scanner/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
82 changes: 31 additions & 51 deletions cloudrun-malware-scanner/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<Config>}
*/
export async function readAndVerifyConfig(
configFile: string,
Expand All @@ -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<Config>}
* Validate and freeze the Config object.
*/
async function validateConfig(config: any, storage: Storage): Promise<Config> {
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');
}
Expand All @@ -113,12 +93,12 @@ async function validateConfig(config: any, storage: Storage): Promise<Config> {
// 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,
))
) {
Expand All @@ -130,13 +110,15 @@ async function validateConfig(config: any, storage: Storage): Promise<Config> {
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,
))
Expand Down Expand Up @@ -165,15 +147,17 @@ async function validateConfig(config: any, storage: Storage): Promise<Config> {
} 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;

// Each element can either be a simple pattern:
// "^.*\\.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<string>;
if (typeof element === 'string') {
// validate regex as simple string
pattern = element;
Expand All @@ -198,10 +182,10 @@ async function validateConfig(config: any, storage: Storage): Promise<Config> {
} 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;
}
Expand All @@ -215,16 +199,12 @@ async function validateConfig(config: any, storage: Storage): Promise<Config> {
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<boolean>}
*/
async function checkBucketExists(
bucketName: string,
Expand All @@ -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;
Expand Down
27 changes: 18 additions & 9 deletions cloudrun-malware-scanner/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -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,
);
9 changes: 5 additions & 4 deletions cloudrun-malware-scanner/gcs-proxy-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -34,7 +34,7 @@ let accessTokenRefreshTimeout: NodeJS.Timeout | null = null;

let clamCvdMirrorBucket = 'uninitialized';

async function accessTokenRefresh(): Promise<void> {
async function accessTokenRefresh() {
if (accessTokenRefreshTimeout) {
clearTimeout(accessTokenRefreshTimeout);
accessTokenRefreshTimeout = null;
Expand All @@ -60,6 +60,7 @@ async function accessTokenRefresh(): Promise<void> {
`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(),
);
Expand Down Expand Up @@ -98,7 +99,7 @@ function handleProxyReq(
}
}

async function setupGcsReverseProxy(): Promise<void> {
function setupGcsReverseProxy() {
const proxy = httpProxy.createProxyServer({
target: 'https://storage.googleapis.com/',
changeOrigin: true,
Expand Down Expand Up @@ -135,7 +136,7 @@ async function run(): Promise<void> {
clamCvdMirrorBucket = config.ClamCvdMirrorBucket;

await accessTokenRefresh();
await setupGcsReverseProxy();
setupGcsReverseProxy();
}

// Start the service, exiting on error.
Expand Down
5 changes: 3 additions & 2 deletions cloudrun-malware-scanner/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -108,6 +108,7 @@ class DiagToPinoLogger implements OpenTelemetryApi.DiagLogger {
logger.error('otel: ' + message, args);
}
}
/* eslint-enable */
}

OpenTelemetryApi.default.diag.setLogger(new DiagToPinoLogger(), {
Expand Down Expand Up @@ -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');
}
Expand Down
Loading

0 comments on commit bbaaadc

Please sign in to comment.