diff --git a/.gitignore b/.gitignore index bc217c5e4f..f4a146b0f5 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ __outputs__ /ts-binary-wrapper/README.md /ts-binary-wrapper/SECURITY.md /ts-binary-wrapper/src/generated +/ts-binary-wrapper/node_modules # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/ts-binary-wrapper/src/bootstrap.ts b/ts-binary-wrapper/src/bootstrap.ts index 19306c4344..61e6de0149 100644 --- a/ts-binary-wrapper/src/bootstrap.ts +++ b/ts-binary-wrapper/src/bootstrap.ts @@ -10,10 +10,11 @@ const errorContextMessage = 'Download Error'; if (process.argv.includes('exec')) { const filenameShasum = config.getShasumFile(); - const downloadUrl = config.getDownloadLocation(); + const { downloadUrl, backupUrl } = config.getDownloadLocations(); - const downloadError = await common.downloadExecutable( + const downloadError = await common.downloadWithBackup( downloadUrl, + backupUrl, executable, filenameShasum, ); diff --git a/ts-binary-wrapper/src/common.ts b/ts-binary-wrapper/src/common.ts index 793e487f30..46ef326bcd 100644 --- a/ts-binary-wrapper/src/common.ts +++ b/ts-binary-wrapper/src/common.ts @@ -21,6 +21,7 @@ const binaryDeploymentsFilePath = path.join( 'generated', 'binary-deployments.json', ); +export const integrationName = 'TS_BINARY_WRAPPER'; export class WrapperConfiguration { private version: string; @@ -45,9 +46,14 @@ export class WrapperConfiguration { return this.binaryName; } - public getDownloadLocation(): string { - const baseUrl = 'https://static.snyk.io/cli/v'; - return baseUrl + this.version + '/' + this.binaryName; + public getDownloadLocations(): { downloadUrl: string; backupUrl: string } { + const baseUrl = 'https://downloads.snyk.io/cli'; + const backupUrl = 'https://static.snyk.io/cli'; + + return { + downloadUrl: `${baseUrl}/v${this.version}/${this.binaryName}`, + backupUrl: `${backupUrl}/v${this.version}/${this.binaryName}`, + }; } public getLocalLocation(): string { @@ -60,6 +66,10 @@ export class WrapperConfiguration { } } +const logErrorWithTimeStamps = (...args) => { + console.error(`${new Date().toISOString()}:`, ...args); +}; + export function determineBinaryName(platform: string, arch: string): string { let osname = platform; let archname = arch; @@ -174,24 +184,33 @@ export function runWrapper(executable: string, cliArguments: string[]): number { const debug = debugEnabled(cliArguments); if (debug) { - console.error('Executing: ' + executable + ' ' + cliArguments.join(' ')); + logErrorWithTimeStamps( + 'Executing: ' + executable + ' ' + cliArguments.join(' '), + ); } const res = spawnSync(executable, cliArguments, { shell: false, stdio: 'inherit', + env: { + ...process.env, + SNYK_INTEGRATION_NAME: integrationName, + SNYK_INTEGRATION_VERSION: getCurrentVersion(versionFile), + }, }); if (res.status !== null) { if (debug) { - console.error(res); + logErrorWithTimeStamps(res); } return res.status; } else { - console.error(res); + logErrorWithTimeStamps(res); if (!formatErrorMessage((res.error as SpawnError).code)) { - console.error('Failed to spawn child process. (' + executable + ')'); + logErrorWithTimeStamps( + 'Failed to spawn child process. (' + executable + ')', + ); } return 2; @@ -232,7 +251,7 @@ export function formatErrorMessage(message: string): boolean { return false; } - console.error(getWarningMessage(warning)); + logErrorWithTimeStamps(getWarningMessage(warning)); return true; } @@ -242,7 +261,8 @@ export function downloadExecutable( filenameShasum: string, ): Promise { return new Promise(function(resolve) { - const options = new URL(downloadUrl); + logErrorWithTimeStamps('Starting download'); + const options = new URL(`${downloadUrl}?utm_source=${integrationName}`); const temp = path.join(__dirname, Date.now().toString()); const fileStream = fs.createWriteStream(temp); const shasum = createHash('sha256').setEncoding('hex'); @@ -271,19 +291,19 @@ export function downloadExecutable( if (filenameShasum && actualShasum != filenameShasum) { cleanupAfterError(Error('Shasum comparison failed!\n' + debugMessage)); } else { - console.error(debugMessage); + logErrorWithTimeStamps(debugMessage); // finally rename the file and change permissions fs.renameSync(temp, filename); fs.chmodSync(filename, 0o755); - console.error('Downloaded successfull! '); + logErrorWithTimeStamps('Downloaded successfull! '); } resolve(undefined); }); - console.error( - "Downloading from '" + downloadUrl + "' to '" + filename + "'", + logErrorWithTimeStamps( + "Downloading from '" + options.toString() + "' to '" + filename + "'", ); const req = https.get(options, (res) => { @@ -322,9 +342,42 @@ export function downloadExecutable( }); } +export async function downloadWithBackup( + downloadUrl: string, + backupUrl: string, + filename: string, + filenameShasum: string, +): Promise { + try { + const error = await downloadExecutable( + downloadUrl, + filename, + filenameShasum, + ); + if (error) { + logErrorWithTimeStamps(error); + logErrorWithTimeStamps( + `Failed to download from ${downloadUrl}! Trying to download from ${backupUrl} location...`, + ); + const backupError = await downloadExecutable( + backupUrl, + filename, + filenameShasum, + ); + + logErrorWithTimeStamps(backupError); + return backupError; + } + } catch (err) { + // Handle any unexpected errors + logErrorWithTimeStamps('An unexpected error occurred:', err); + throw err; // Rethrow if you want to propagate the error upwards + } +} + export async function logError( context: string, - err, + err: Error, printToConsole = true, ): Promise { if (isAnalyticsEnabled()) { @@ -345,7 +398,7 @@ export async function logError( // finally log the error to the console as well if (printToConsole) { - console.error('\n' + err); + logErrorWithTimeStamps('\n' + err); formatErrorMessage(err.message); } } diff --git a/ts-binary-wrapper/src/index.ts b/ts-binary-wrapper/src/index.ts index be7eb3cadd..2d3affc183 100644 --- a/ts-binary-wrapper/src/index.ts +++ b/ts-binary-wrapper/src/index.ts @@ -28,12 +28,14 @@ function run(executable: string): number { try { const config = common.getCurrentConfiguration(); const executable = config.getLocalLocation(); + const { downloadUrl, backupUrl } = config.getDownloadLocations(); if (!fs.existsSync(executable)) { console.error("Executable doesn't exist, trying to download."); - const downloadError = await common.downloadExecutable( - config.getDownloadLocation(), + const downloadError = await common.downloadWithBackup( + downloadUrl, + backupUrl, executable, config.getShasumFile(), ); diff --git a/ts-binary-wrapper/test/unit/common.spec.ts b/ts-binary-wrapper/test/unit/common.spec.ts index 26c80a535b..6468b1252c 100644 --- a/ts-binary-wrapper/test/unit/common.spec.ts +++ b/ts-binary-wrapper/test/unit/common.spec.ts @@ -132,7 +132,7 @@ describe('Get Shasum', () => { describe('Configuration', () => { it('Download and local location', async () => { const expectedDownloadLocation = - 'https://static.snyk.io/cli/v1.2.3/snyk-win.exe'; + 'https://downloads.snyk.io/cli/v1.2.3/snyk-win.exe'; const expectedLocalLocation = path.join( __dirname, '..', @@ -146,7 +146,7 @@ describe('Configuration', () => { '1234abcdef', ); - const actualDownloadLocation = config.getDownloadLocation(); + const actualDownloadLocation = config.getDownloadLocations().downloadUrl; expect(actualDownloadLocation).toEqual(expectedDownloadLocation); const actualLocalLocation = config.getLocalLocation(); @@ -219,7 +219,7 @@ describe('Testing binary bootstrapper', () => { // download the shasum first, here we don't expect a shasum comparison const shasumDownload = await common.downloadExecutable( - config.getDownloadLocation() + shafileExtension, + config.getDownloadLocations().downloadUrl + shafileExtension, shasumFile, '', ); @@ -227,9 +227,10 @@ describe('Testing binary bootstrapper', () => { expect(fs.existsSync(shasumFile)).toBeTruthy(); const expectedShasum = common.getCurrentSha256sum(binaryName, shasumFile); + const { downloadUrl } = config.getDownloadLocations(); // download binary next and use previously downloaded shasum to check validity const binaryDownload = await common.downloadExecutable( - config.getDownloadLocation(), + downloadUrl, config.getLocalLocation(), expectedShasum, ); @@ -241,10 +242,55 @@ describe('Testing binary bootstrapper', () => { try { // check if the binary is executable - fs.accessSync(config.getLocalLocation(), fs.constants.X_OK); + expect( + fs.accessSync(config.getLocalLocation(), fs.constants.X_OK), + ).not.toThrow(); + } catch { + // execution of binary not possible + } + + fs.unlinkSync(shasumFile); + fs.unlinkSync(config.getLocalLocation()); + }); + it('downloadWithBackup() succesfull', async () => { + const binaryName = 'snyk-macos'; + const shafileExtension = '.sha256'; + const config = new common.WrapperConfiguration('1.1080.0', binaryName, ''); + const shasumFile = + config.getLocalLocation() + Math.random() + shafileExtension; + const { downloadUrl } = config.getDownloadLocations(); + + // download the shasum first, here we don't expect a shasum comparison + const shasumDownload = await common.downloadWithBackup( + 'https://notdownloads.snyk.io/cli/v1.1080.0/snyk-macos.sha256', + downloadUrl + shafileExtension, + shasumFile, + '', + ); + expect(shasumDownload).toBeUndefined(); + expect(fs.existsSync(shasumFile)).toBeTruthy(); + const expectedShasum = common.getCurrentSha256sum(binaryName, shasumFile); + + // download binary next and use previously downloaded shasum to check validity + const binaryDownload = await common.downloadWithBackup( + 'https://notdownloads.snyk.io/cli/v1.1080.0/snyk-macos', + downloadUrl, + config.getLocalLocation(), + expectedShasum, + ); + expect(binaryDownload).toBeUndefined(); + expect(fs.existsSync(config.getLocalLocation())).toBeTruthy(); + + const stats = fs.statSync(config.getLocalLocation()); + expect(stats.mode).toEqual(0o100755); + + try { + // check if the binary is executable + expect( + fs.accessSync(config.getLocalLocation(), fs.constants.X_OK), + ).not.toThrow(); } catch { // execution of binary not possible - expect(false).toBeTruthy(); } fs.unlinkSync(shasumFile); @@ -257,10 +303,11 @@ describe('Testing binary bootstrapper', () => { const config = new common.WrapperConfiguration('1.1080.0', binaryName, ''); const shasumFile = config.getLocalLocation() + Math.random() + shafileExtension; + const { downloadUrl } = config.getDownloadLocations(); // download just any file and state a shasum expectation that never can be fullfilled const shasumDownload = await common.downloadExecutable( - config.getDownloadLocation() + shafileExtension, + downloadUrl + shafileExtension, shasumFile, 'incorrect-shasum', ); @@ -274,10 +321,11 @@ describe('Testing binary bootstrapper', () => { const config = new common.WrapperConfiguration('1.1080.0', binaryName, ''); const shasumFile = config.getLocalLocation() + Math.random() + shafileExtension; + const { downloadUrl } = config.getDownloadLocations(); // try to download a file that doesn't exis const shasumDownload = await common.downloadExecutable( - config.getDownloadLocation() + shafileExtension, + downloadUrl + shafileExtension, shasumFile, 'incorrect-shasum', ); @@ -298,7 +346,7 @@ describe('Testing binary bootstrapper', () => { }); }); -describe('isAnalyticsEnabled ', () => { +describe('isAnalyticsEnabled', () => { it('enabled', async () => { delete process.env.SNYK_DISABLE_ANALYTICS; expect(common.isAnalyticsEnabled()).toBeTruthy(); diff --git a/ts-binary-wrapper/test/util/prepareEnvironment.ts b/ts-binary-wrapper/test/util/prepareEnvironment.ts index cd624529fa..65e45ff594 100644 --- a/ts-binary-wrapper/test/util/prepareEnvironment.ts +++ b/ts-binary-wrapper/test/util/prepareEnvironment.ts @@ -79,6 +79,6 @@ export class TestEnvironmentSetup { if (process.argv.includes('exec')) { (async function() { const env = new TestEnvironmentSetup(); - await env.prepareEnvironment('1.1080.0'); - }); + await env.prepareEnvironment('1.1292.1'); + })(); }