Skip to content

Commit 5ee1481

Browse files
committed
implemented httpClient and Client interface. Added arm support for linux machines
1 parent b2a16cb commit 5ee1481

File tree

4 files changed

+114
-73
lines changed

4 files changed

+114
-73
lines changed

src/main/client/Client.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export interface Client {
2+
/**
3+
* Downloads a file from the given URL and saves it to the specified output path.
4+
*
5+
* @param url - The URL to download the file from.
6+
* @param outputPath - The path where the downloaded file will be saved.
7+
* @throws An error if the download fails.
8+
*/
9+
downloadFile(url: string, outputPath: string): Promise<void>;
10+
getProxyConfig(): any;
11+
}

src/main/client/HttpClient.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import axios, {AxiosRequestConfig} from 'axios';
2+
import {logger} from '../wrapper/loggerConfig';
3+
import * as fs from 'fs';
4+
import {finished} from 'stream/promises';
5+
import {Client} from "./Client";
6+
7+
export class HttpClient implements Client {
8+
private readonly axiosConfig: AxiosRequestConfig;
9+
10+
constructor() {
11+
this.axiosConfig = {
12+
responseType: 'stream',
13+
proxy: this.getProxyConfig(),
14+
};
15+
}
16+
17+
public getProxyConfig() {
18+
const proxyUrl = process.env.HTTP_PROXY;
19+
if (proxyUrl) {
20+
logger.info(`Detected proxy configuration in HTTP_PROXY`);
21+
const parsedProxy = new URL(proxyUrl);
22+
23+
return {
24+
host: parsedProxy.hostname,
25+
port: parseInt(parsedProxy.port, 10),
26+
protocol: parsedProxy.protocol.replace(':', ''), // remove the colon
27+
auth: parsedProxy.username && parsedProxy.password
28+
? {username: parsedProxy.username, password: parsedProxy.password}
29+
: undefined,
30+
};
31+
}
32+
logger.info('No proxy configuration detected.');
33+
return undefined;
34+
}
35+
36+
public async downloadFile(url: string, outputPath: string): Promise<void> {
37+
logger.info(`Starting download from URL: ${url}`);
38+
const writer = fs.createWriteStream(outputPath);
39+
40+
try {
41+
if (this.axiosConfig.proxy) {
42+
logger.info(
43+
`Using proxy - Host: ${this.axiosConfig.proxy.host}, Port: ${this.axiosConfig.proxy.port},` +
44+
`Protocol: ${this.axiosConfig.proxy.protocol}, Auth: ${this.axiosConfig.proxy.auth ? 'Yes' : 'No'}`
45+
);
46+
}
47+
const response = await axios({...this.axiosConfig, url});
48+
response.data.pipe(writer);
49+
await finished(writer);
50+
logger.info(`Download completed successfully. File saved to: ${outputPath}`);
51+
} catch (error) {
52+
logger.error(`Error downloading file from ${url}: ${error.message || error}`);
53+
throw error;
54+
} finally {
55+
writer.close();
56+
logger.info('Write stream closed.');
57+
}
58+
}
59+
}

src/main/osinstaller/CxInstaller.ts

Lines changed: 42 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,65 @@ import * as fsPromises from 'fs/promises';
22
import * as fs from 'fs';
33
import * as path from 'path';
44
import * as tar from 'tar';
5-
import axios, {AxiosRequestConfig} from 'axios';
65
import * as unzipper from 'unzipper';
76
import {logger} from "../wrapper/loggerConfig";
8-
import {finished} from 'stream/promises';
7+
import {Client} from "../client/Client";
98

109
type SupportedPlatforms = 'win32' | 'darwin' | 'linux';
1110

11+
interface PlatformData {
12+
platform: string;
13+
extension: string;
14+
}
15+
1216
export class CxInstaller {
13-
private readonly platform: string;
17+
private readonly platform: SupportedPlatforms;
1418
private cliVersion: string;
1519
private readonly resourceDirPath: string;
1620
private readonly installedCLIVersionFileName = 'cli-version';
17-
private readonly cliDefaultVersion = '2.2.5'; // This will be used if the version file is not found. Should be updated with the latest version.
18-
19-
constructor(platform: string) {
20-
this.platform = platform;
21-
this.resourceDirPath = path.join(__dirname, `../wrapper/resources`);
21+
private readonly cliDefaultVersion = '2.2.5'; // Update this with the latest version.
22+
private readonly client;
23+
24+
private static readonly PLATFORMS: Record<SupportedPlatforms, PlatformData> = {
25+
win32: { platform: 'windows', extension: 'zip' },
26+
darwin: { platform: 'darwin', extension: 'tar.gz' },
27+
linux: { platform: 'linux', extension: 'tar.gz' }
28+
};
29+
30+
constructor(platform: string, client: Client) {
31+
this.platform = platform as SupportedPlatforms;
32+
this.resourceDirPath = path.join(__dirname, '../wrapper/resources');
33+
this.client = client;
2234
}
2335

2436
private async getDownloadURL(): Promise<string> {
2537
const cliVersion = await this.readASTCLIVersion();
38+
const platformData = CxInstaller.PLATFORMS[this.platform];
2639

27-
const platforms: Record<SupportedPlatforms, { platform: string; extension: string }> = {
28-
win32: {platform: 'windows', extension: 'zip'},
29-
darwin: {platform: 'darwin', extension: 'tar.gz'},
30-
linux: {platform: 'linux', extension: 'tar.gz'}
31-
};
32-
33-
const platformKey = this.platform as SupportedPlatforms;
34-
35-
const platformData = platforms[platformKey];
3640
if (!platformData) {
3741
throw new Error('Unsupported platform or architecture');
3842
}
3943

40-
return `https://download.checkmarx.com/CxOne/CLI/${cliVersion}/ast-cli_${cliVersion}_${platformData.platform}_x64.${platformData.extension}`;
44+
const arch = this.getArchitecture();
45+
logger.info(`Platform: ${this.platform}, Arch: ${arch}`);
46+
47+
return `https://download.checkmarx.com/CxOne/CLI/${cliVersion}/ast-cli_${cliVersion}_${platformData.platform}_${arch}.${platformData.extension}`;
48+
}
49+
50+
private getArchitecture(): string {
51+
// For non-linux platforms we default to x64.
52+
if (this.platform !== 'linux') {
53+
return 'x64';
54+
}
55+
56+
switch (process.arch) {
57+
case 'arm64':
58+
return 'arm64';
59+
case 'arm':
60+
return 'armv6';
61+
default:
62+
return 'x64';
63+
}
4164
}
4265

4366
public getExecutablePath(): string {
@@ -62,7 +85,7 @@ export class CxInstaller {
6285
const url = await this.getDownloadURL();
6386
const zipPath = path.join(this.resourceDirPath, this.getCompressFolderName());
6487

65-
await this.downloadFile(url, zipPath);
88+
await this.client.downloadFile(url, zipPath);
6689
logger.info('Downloaded CLI to:', zipPath);
6790

6891
await this.extractArchive(zipPath, this.resourceDirPath);
@@ -147,59 +170,6 @@ export class CxInstaller {
147170
}
148171
}
149172

150-
private async downloadFile(url: string, outputPath: string) {
151-
logger.info(`Starting download from URL: ${url}`);
152-
const writer = fs.createWriteStream(outputPath);
153-
154-
try {
155-
// Create base Axios configuration
156-
const axiosConfig: AxiosRequestConfig = {
157-
url,
158-
responseType: 'stream',
159-
};
160-
161-
// Configure proxy if HTTP_PROXY environment variable is set
162-
const proxyUrl = process.env.HTTP_PROXY;
163-
if (proxyUrl) {
164-
logger.info(`Detected proxy configuration in HTTP_PROXY`);
165-
const parsedProxy = new URL(proxyUrl);
166-
167-
axiosConfig.proxy = {
168-
host: parsedProxy.hostname,
169-
port: parseInt(parsedProxy.port, 10),
170-
protocol: parsedProxy.protocol.replace(':', ''), // remove the colon
171-
auth: parsedProxy.username && parsedProxy.password
172-
? {username: parsedProxy.username, password: parsedProxy.password}
173-
: undefined, // Only include auth if credentials are provided
174-
};
175-
176-
logger.info(
177-
`Using proxy - Host: ${axiosConfig.proxy.host}, Port: ${axiosConfig.proxy.port}, ` +
178-
`Protocol: ${axiosConfig.proxy.protocol}, Auth: ${axiosConfig.proxy.auth ? 'Yes' : 'No'}`
179-
);
180-
} else {
181-
logger.info('No proxy configuration detected.');
182-
}
183-
184-
// Perform the download request
185-
logger.info(`Initiating download request to: ${url}`);
186-
const response = await axios(axiosConfig);
187-
188-
// Pipe the response data to the output file
189-
response.data.pipe(writer);
190-
191-
// Await the completion of the write stream
192-
await finished(writer);
193-
logger.info(`Download completed successfully. File saved to: ${outputPath}`);
194-
} catch (error) {
195-
logger.error(`Error downloading file from ${url}: ${error.message || error}`);
196-
} finally {
197-
writer.close();
198-
logger.info('Write stream closed.');
199-
}
200-
}
201-
202-
203173
private checkExecutableExists(): boolean {
204174
return fs.existsSync(this.getExecutablePath());
205175
}

src/main/wrapper/CxWrapper.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as os from "os";
88
import CxBFL from "../bfl/CxBFL";
99
import {CxInstaller} from "../osinstaller/CxInstaller";
1010
import {Semaphore} from "async-mutex";
11+
import {HttpClient} from "../client/HttpClient";
1112

1213

1314
type ParamTypeMap = Map<CxParamType, string>;
@@ -18,7 +19,7 @@ export class CxWrapper {
1819
config: CxConfig;
1920
cxInstaller: CxInstaller;
2021
private constructor(cxScanConfig: CxConfig, logFilePath?: string) {
21-
this.cxInstaller = new CxInstaller(process.platform);
22+
this.cxInstaller = new CxInstaller(process.platform, new HttpClient());
2223
this.config = new CxConfig();
2324
getLoggerWithFilePath(logFilePath)
2425
if (cxScanConfig.apiKey) {

0 commit comments

Comments
 (0)