Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 47 additions & 6 deletions .github/workflows/update-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,56 @@ jobs:
run: |
echo ${{ steps.checkmarx-ast-cli.outputs.release_tag }} > checkmarx-ast-cli.version

# Update the TypeScript file's cliDefaultVersion field
- name: Update cliDefaultVersion in CxInstaller.ts
# Download CLI binaries and generate checksums
- name: Download CLI and generate checksums
if: steps.checkmarx-ast-cli.outputs.current_tag != steps.checkmarx-ast-cli.outputs.release_tag
env:
NEW_CLI_VERSION: ${{ steps.checkmarx-ast-cli.outputs.release_tag }}
RELEASE_TAG: ${{ steps.checkmarx-ast-cli.outputs.release_tag }}
run: |
FILE_PATH="src/main/osinstaller/CxInstaller.ts"
# Ensure that 'cliDefaultVersion' is updated correctly
sed -i "s/\(cliDefaultVersion = '\)[^']*\(';\)/\1${NEW_CLI_VERSION}\2/" $FILE_PATH
VERSION=$RELEASE_TAG

# Initialize checksums object
CHECKSUMS='{}'

# Platform configurations: platform_name,architecture,extension,os_platform
PLATFORMS=(
"windows,x64,zip,windows"
"darwin,x64,tar.gz,darwin"
"linux,x64,tar.gz,linux"
"linux,arm64,tar.gz,linux"
"linux,armv6,tar.gz,linux"
)

for PLATFORM_CONFIG in "${PLATFORMS[@]}"; do
IFS=',' read -r OS_TYPE ARCH EXT OS_PLATFORM <<< "$PLATFORM_CONFIG"

KEY="${OS_PLATFORM}_${ARCH}"
URL="https://download.checkmarx.com/CxOne/CLI/${VERSION}/ast-cli_${VERSION}_${OS_PLATFORM}_${ARCH}.${EXT}"

echo "Downloading checksum for ${KEY} from ${URL}..."

# Download binary
TEMP_FILE="/tmp/ast-cli_${KEY}.${EXT}"
if curl -sL -o "$TEMP_FILE" "$URL"; then
# Calculate SHA-256
CHECKSUM=$(sha256sum "$TEMP_FILE" | awk '{print $1}')
echo "✓ ${KEY}: ${CHECKSUM}"

# Update checksums JSON
CHECKSUMS=$(echo "$CHECKSUMS" | jq --arg key "$KEY" --arg value "$CHECKSUM" '.[$key] = $value')

# Cleanup
rm -f "$TEMP_FILE"
else
echo "✗ Failed to download ${KEY}"
exit 1
fi
done

# Write checksums to file
echo "$CHECKSUMS" | jq '.' > checkmarx-ast-cli.checksums
echo "Checksums updated:"
cat checkmarx-ast-cli.checksums

# Create a Pull Request with the version changes
- name: Create Pull Request
Expand Down
51 changes: 19 additions & 32 deletions src/main/osinstaller/CxInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface PlatformData {
export class CxInstaller {
private readonly platform: SupportedPlatforms;
private cliVersion: string;
private cliChecksum: string | null;
private cliChecksum: string;
private readonly resourceDirPath: string;
private readonly installedCLIVersionFileName = 'cli-version';
private readonly client: AstClient;
Expand All @@ -32,16 +32,6 @@ export class CxInstaller {
linux: { platform: linuxOS, extension: 'tar.gz' }
};

// Default version and its paired SHA-256 checksums, keyed by "platform_architecture".
// Update both together when bumping the default CLI version.
private readonly cliDefaultVersion = '2.3.48';
private static readonly cliDefaultChecksums: Record<string, string> = {
'windows_x64': '441ee8df46cc630ae000f8ba73925113aeed8c4d16cf274944aff3e7197e3470',
'darwin_x64': 'b72f7e4ca14e5e56600b07d22c848a4b85e7c37d2e595424340cc699ea10006b',
'linux_x64': 'eb3eb55add37f150188f5a8b36b2a659f902ad9569dcb7ee652531fe525022e2',
'linux_arm64': '7df61689b3c2bbd4c27face5bdc0da97f63e4533229d6b53dd777f90d3904931',
'linux_armv6': '99659f2e0804b197550efc6a9ddb6029babc980d32bdfeeb508199247ac95878'
};

constructor(platform: string, client: AstClient) {
this.platform = platform as SupportedPlatforms;
Expand All @@ -50,10 +40,9 @@ export class CxInstaller {
}

// Returns the CLI version and its platform-specific SHA-256 checksum.
// Tries the version file and checksums file first; falls back to the
// hardcoded defaults if the version file is absent or empty.
// Throws CxError if the version or checksums file is missing, empty, or has no entry for the current platform.
// Result is cached after the first read.
async readASTCLIVersion(): Promise<{ version: string; checksum: string | null }> {
async readASTCLIVersion(): Promise<{ version: string; checksum: string }> {
if (this.cliVersion) {
return { version: this.cliVersion, checksum: this.cliChecksum };
}
Expand All @@ -62,30 +51,28 @@ export class CxInstaller {
const architecture = this.getArchitecture();
const key = `${platformData.platform}_${architecture}`;

let version: string | null = null;
let version: string;
try {
const content = await fsPromises.readFile(this.getVersionFilePath(), 'utf-8');
const trimmed = content.trim();
if (trimmed) version = trimmed;
} catch {
// version file absent — fall through to defaults
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we have empty catch block ?

if (!trimmed) throw new CxError('CLI version not found');
version = trimmed;
} catch (error) {
if (error instanceof CxError) throw error;
throw new CxError('CLI version not found');
}

let checksum: string | null;
if (version === null) {
version = this.cliDefaultVersion;
checksum = CxInstaller.cliDefaultChecksums[key] ?? null;
} else {
try {
const content = await fsPromises.readFile(this.getChecksumsFilePath(), 'utf-8');
checksum = (JSON.parse(content) as Record<string, string>)[key] ?? null;
if (checksum === null) {
logger.warn(`No checksum found for ${key} in checksums file. Download will not be verified.`);
}
} catch {
logger.warn(`Checksums file not found. Download of version ${version} will not be verified.`);
checksum = null;
let checksum: string;
try {
const content = await fsPromises.readFile(this.getChecksumsFilePath(), 'utf-8');
const parsed = (JSON.parse(content) as Record<string, string>);
if (!parsed[key]) {
throw new CxError(`No checksum found for ${key} in checksums file.`);
}
checksum = parsed[key];
} catch (error) {
if (error instanceof CxError) throw error;
throw new CxError(`Checksums file not found. Download of version ${version} will not be verified.`);
}

this.cliVersion = version;
Expand Down
29 changes: 13 additions & 16 deletions src/tests/CxInstallerTest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { anyString, mock, instance, when, verify } from "ts-mockito";
import { AstClient } from "../main/client/AstClient";
import * as fs from "fs";
import * as crypto from "crypto";
import * as path from "path";

// Mock AstClient and set up an instance from it
const astClientMock = mock(AstClient);
Expand All @@ -15,41 +16,44 @@ const cxInstallerWindows = new CxInstaller("win32", astClientInstance);

describe("CxInstaller cases", () => {
it('CxInstaller getDownloadURL Linux Successful case', async () => {
const testVersion = '2.3.48';
jest.spyOn(cxInstallerLinux as any, 'readASTCLIVersion').mockResolvedValue({ version: testVersion, checksum: 'mock-checksum' });
const { url } = await cxInstallerLinux.getDownloadURL();
const { version } = await cxInstallerLinux.readASTCLIVersion();
const architecture = getArchitecture(cxInstallerLinux.getPlatform());
expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${version}/ast-cli_${version}_linux_${architecture}.tar.gz`);
expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${testVersion}/ast-cli_${testVersion}_linux_${architecture}.tar.gz`);
});

it('CxInstaller getDownloadURL Mac Successful case', async () => {
const testVersion = '2.3.48';
jest.spyOn(cxInstallerMac as any, 'readASTCLIVersion').mockResolvedValue({ version: testVersion, checksum: 'mock-checksum' });
const { url } = await cxInstallerMac.getDownloadURL();
const { version } = await cxInstallerLinux.readASTCLIVersion();
const architecture = getArchitecture(cxInstallerMac.getPlatform());
expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${version}/ast-cli_${version}_darwin_${architecture}.tar.gz`);
expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${testVersion}/ast-cli_${testVersion}_darwin_${architecture}.tar.gz`);
});

it('CxInstaller getDownloadURL Windows Successful case', async () => {
const testVersion = '2.3.48';
jest.spyOn(cxInstallerWindows as any, 'readASTCLIVersion').mockResolvedValue({ version: testVersion, checksum: 'mock-checksum' });
const { url } = await cxInstallerWindows.getDownloadURL();
const { version } = await cxInstallerLinux.readASTCLIVersion();
const architecture = getArchitecture(cxInstallerWindows.getPlatform());
expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${version}/ast-cli_${version}_windows_${architecture}.zip`);
expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${testVersion}/ast-cli_${testVersion}_windows_${architecture}.zip`);
});
});

describe("CxInstaller getExecutablePath cases", () => {
it('CxInstaller getExecutablePath Linux Successful case', () => {
const executablePath = cxInstallerLinux.getExecutablePath();
expect(executablePath).toContain(`src/main/wrapper/resources/cx`);
expect(executablePath).toContain(path.join('src', 'main', 'wrapper', 'resources', 'cx'));
});

it('CxInstaller getExecutablePath Mac Successful case', () => {
const executablePath = cxInstallerMac.getExecutablePath();
expect(executablePath).toContain(`src/main/wrapper/resources/cx`);
expect(executablePath).toContain(path.join('src', 'main', 'wrapper', 'resources', 'cx'));
});

it('CxInstaller getExecutablePath Windows Successful case', () => {
const executablePath = cxInstallerWindows.getExecutablePath();
expect(executablePath).toContain(`src/main/wrapper/resources/cx.exe`);
expect(executablePath).toContain(path.join('src', 'main', 'wrapper', 'resources', 'cx.exe'));
});
});

Expand Down Expand Up @@ -140,13 +144,6 @@ describe("CxInstaller checksum verification cases", () => {
expect(exitSpy).toHaveBeenCalledWith(1);
});

it('CxInstaller null checksum skips verification', async () => {
jest.spyOn(localLinux as any, 'readASTCLIVersion').mockResolvedValue({ version: '9.9.99', checksum: null });
when(localMock.downloadFile(anyString(), anyString())).thenResolve();
await localLinux.downloadIfNotInstalledCLI();
expect(exitSpy).not.toHaveBeenCalled();
});

it('CxInstaller CX_CLI_LOCATION skips checksum verification', async () => {
process.env.CX_CLI_LOCATION = 'https://internal.example.com/cli';
jest.spyOn(localLinux as any, 'readASTCLIVersion').mockResolvedValue({ version: '2.3.48', checksum: 'irrelevant' });
Expand Down
Loading