diff --git a/Makefile b/Makefile index 010300b4f1..7746723704 100644 --- a/Makefile +++ b/Makefile @@ -197,9 +197,12 @@ $(BINARY_WRAPPER_DIR)/src/generated/version: $(BINARY_WRAPPER_DIR)/src/generated $(BINARY_WRAPPER_DIR)/src/generated/sha256sums.txt: @echo "-- Generating $(@F)" @cat $(BINARY_OUTPUT_FOLDER)/*.sha256 > $(BINARY_WRAPPER_DIR)/src/generated/sha256sums.txt + @for f in $(BINARY_OUTPUT_FOLDER)/experimental/*.sha256; do \ + sed "s| \(.*\)| experimental/\1|" "$$f"; \ + done >> $(BINARY_WRAPPER_DIR)/src/generated/sha256sums.txt .PHONY: build-binary-wrapper -build-binary-wrapper: pre-build-binary-wrapper $(BINARY_WRAPPER_DIR)/src/generated/version $(BINARY_WRAPPER_DIR)/src/generated/sha256sums.txt +build-binary-wrapper: pre-build-binary-wrapper $(BINARY_WRAPPER_DIR)/src/generated/version $(BINARY_WRAPPER_DIR)/src/generated/sha256sums.txt $(BINARY_RELEASES_FOLDER_TS_CLI)/experimental/snyk-linux $(BINARY_RELEASES_FOLDER_TS_CLI)/experimental/snyk-linux-arm64 @echo "-- Building Typescript Binary Wrapper ($(BINARY_WRAPPER_DIR)/dist/)" @cd $(BINARY_WRAPPER_DIR); npm run build diff --git a/binary-deployments.json b/binary-deployments.json index 2febdc2abf..5447483923 100644 --- a/binary-deployments.json +++ b/binary-deployments.json @@ -13,5 +13,9 @@ "linux": { "amd64": "snyk-linux", "arm64": "snyk-linux-arm64" + }, + "linuxstatic": { + "amd64": "experimental/snyk-linux", + "arm64": "experimental/snyk-linux-arm64" } } diff --git a/scripts/build-ts-binary-wrapper-locally.go b/scripts/build-ts-binary-wrapper-locally.go index 7d6624cd9c..55a165d80d 100644 --- a/scripts/build-ts-binary-wrapper-locally.go +++ b/scripts/build-ts-binary-wrapper-locally.go @@ -18,11 +18,22 @@ const ( nc = "\033[0m" // No Color ) -// Configuration +/* +Parameters can be retrieved from https://github.com/snyk/cli/releases +* version: the version of the snyk binary to download +* platform: the platform of the snyk binary to download +* sha256Hash: the sha256 hash of the snyk binary to download + +Linux static specific parameters are optional and only used when building for linux static targets +* linuxstaticPlatform: the linux static platform of the snyk binary to download [snyk-linux, snyk-linux-arm64] +* linuxstaticSha256Hash: the sha256 hash of the linuxstatic snyk binary to download. Retrieved from http://downloads.snyk.io/experimental/cli//.sha256 +*/ const ( - version = "1.1298.2" - platform = "snyk-macos-arm64" - sha256Hash = "0a5016d8ec007483fc3397ea06c4f655a3771c3bee34fb778b8bb12c5b19ed9a" + version = "1.1304.2" + platform = "snyk-linux-arm64" + sha256Hash = "79391210b25e50297db2cec379ccf5ed16f484ce16076195c323c26a05c63d1c" + linuxstaticPlatform = "snyk-linux-arm64" + linuxstaticSha256Hash = "7bc078339b4db675f4009257c0cb97713ff8e0c63add204200509832e3ad98b0" ) // Logging functions @@ -49,26 +60,26 @@ func getProjectRoot() (string, error) { if err != nil { return "", err } - + // Check if we're in the scripts directory if filepath.Base(scriptDir) == "scripts" { return filepath.Dir(scriptDir), nil } - + // If not, try to find the project root by looking for key files currentDir, err := os.Getwd() if err != nil { return "", fmt.Errorf("failed to get current directory: %w", err) } - + // Walk up the directory tree to find the project root for dir := currentDir; dir != filepath.Dir(dir); dir = filepath.Dir(dir) { - if fileExists(filepath.Join(dir, "package.json")) && - fileExists(filepath.Join(dir, "Makefile")) { + if fileExists(filepath.Join(dir, "package.json")) && + fileExists(filepath.Join(dir, "Makefile")) { return dir, nil } } - + return "", fmt.Errorf("could not find project root (no package.json and Makefile found)") } @@ -82,19 +93,19 @@ func getScriptDir() (string, error) { } return filepath.Dir(filename), nil } - + // If running as compiled binary, use os.Executable executable, err := os.Executable() if err != nil { return "", fmt.Errorf("failed to get executable path: %w", err) } - + // Resolve symlinks to get the actual path resolvedPath, err := filepath.EvalSymlinks(executable) if err != nil { return "", fmt.Errorf("failed to resolve symlinks: %w", err) } - + return filepath.Dir(resolvedPath), nil } @@ -119,23 +130,23 @@ func dirExists(path string) bool { // Function to check if required tools are available func checkRequirements(projectRoot string) error { logInfo("Checking requirements...") - + if !isCommandAvailable("npm") { return fmt.Errorf("npm is not installed or not in PATH") } - + if !isCommandAvailable("make") { return fmt.Errorf("make is not installed or not in PATH") } - + if !fileExists(filepath.Join(projectRoot, "package.json")) { return fmt.Errorf("package.json not found in project root") } - + if !fileExists(filepath.Join(projectRoot, "Makefile")) { return fmt.Errorf("Makefile not found in project root") } - + logSuccess("All requirements satisfied") return nil } @@ -143,11 +154,11 @@ func checkRequirements(projectRoot string) error { // Function to validate the project structure func validateProject(projectRoot string) error { logInfo("Validating project structure...") - + if !dirExists(projectRoot) { return fmt.Errorf("project root directory not found: %s", projectRoot) } - + logSuccess("Project structure validated") return nil } @@ -157,70 +168,70 @@ func runCommand(name string, args ...string) error { cmd := exec.Command(name, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - + logInfo(fmt.Sprintf("Running: %s %s", name, strings.Join(args, " "))) - + if err := cmd.Run(); err != nil { return fmt.Errorf("command failed: %w", err) } - + return nil } // Write content to a file func writeFile(path, content string) error { logInfo(fmt.Sprintf("Creating file: %s", path)) - + if err := os.WriteFile(path, []byte(content), 0644); err != nil { return fmt.Errorf("failed to write file %s: %w", path, err) } - + return nil } // Create directory if it doesn't exist func createDir(path string) error { logInfo(fmt.Sprintf("Creating directory: %s", path)) - + if err := os.MkdirAll(path, 0755); err != nil { return fmt.Errorf("failed to create directory %s: %w", path, err) } - + return nil } // Main execution func main() { logInfo("Starting build process...") - logWarning(fmt.Sprintf("This script assumes %s, please change it if needed!", platform)) - + logWarning(fmt.Sprintf("This script assumes %s (linuxstatic: %s), please change it if needed!", platform, linuxstaticPlatform)) + // Get project root projectRoot, err := getProjectRoot() if err != nil { logError(fmt.Sprintf("Failed to get project root: %v", err)) os.Exit(1) } - + logInfo(fmt.Sprintf("Project root determined: %s", projectRoot)) - + // Validate environment if err := validateProject(projectRoot); err != nil { logError(err.Error()) os.Exit(1) } - + if err := checkRequirements(projectRoot); err != nil { logError(err.Error()) os.Exit(1) } - + // Change to project root directory logInfo(fmt.Sprintf("Changing to project root: %s", projectRoot)) if err := os.Chdir(projectRoot); err != nil { logError(fmt.Sprintf("Failed to change directory: %v", err)) os.Exit(1) } - + // Install dependencies logInfo("Installing npm dependencies...") if err := runCommand("npm", "ci"); err != nil { @@ -228,21 +239,21 @@ func main() { os.Exit(1) } logSuccess("Dependencies installed successfully") - + // Create binary-releases directory binaryReleasesDir := "binary-releases" if err := createDir(binaryReleasesDir); err != nil { logError(err.Error()) os.Exit(1) } - + // Create version file versionFile := filepath.Join(binaryReleasesDir, "version") if err := writeFile(versionFile, version); err != nil { logError(err.Error()) os.Exit(1) } - + // Create SHA256 file sha256File := filepath.Join(binaryReleasesDir, platform+".sha256") sha256Content := fmt.Sprintf("%s %s", sha256Hash, platform) @@ -250,7 +261,21 @@ func main() { logError(err.Error()) os.Exit(1) } - + + // Create experimental directory and linuxstatic SHA256 file + experimentalDir := filepath.Join(binaryReleasesDir, "experimental") + if err := createDir(experimentalDir); err != nil { + logError(err.Error()) + os.Exit(1) + } + + sha256File2 := filepath.Join(experimentalDir, linuxstaticPlatform+".sha256") + sha256Content2 := fmt.Sprintf("%s %s", linuxstaticSha256Hash, linuxstaticPlatform) + if err := writeFile(sha256File2, sha256Content2); err != nil { + logError(err.Error()) + os.Exit(1) + } + // Build the binary logInfo("Building binary release...") if err := runCommand("make", "binary-releases/snyk.tgz"); err != nil { @@ -258,4 +283,4 @@ func main() { os.Exit(1) } logSuccess("Binary build completed successfully") -} \ No newline at end of file +} diff --git a/ts-binary-wrapper/src/common.ts b/ts-binary-wrapper/src/common.ts index bc9acf56e0..93ac7ba377 100644 --- a/ts-binary-wrapper/src/common.ts +++ b/ts-binary-wrapper/src/common.ts @@ -26,16 +26,16 @@ export const integrationName = 'TS_BINARY_WRAPPER'; export class WrapperConfiguration { private version: string; private binaryName: string; - private expectedSha256sum: string; + private expectedSha256sums: string[]; public constructor( version: string, binaryName: string, - expectedSha256sum: string, + expectedSha256sums: string[], ) { this.version = version; this.binaryName = binaryName; - this.expectedSha256sum = expectedSha256sum; + this.expectedSha256sums = expectedSha256sums; } public getVersion(): string { @@ -61,8 +61,8 @@ export class WrapperConfiguration { return path.join(currentFolder, this.binaryName); } - public getShasumFile(): string { - return this.expectedSha256sum; + public getShasumFile(): string[] { + return this.expectedSha256sums; } } @@ -150,8 +150,28 @@ export function getCurrentSha256sum( export function getCurrentConfiguration(): WrapperConfiguration { const binaryName = determineBinaryName(os.platform(), os.arch()); const version = getCurrentVersion(versionFile); - const expectedSha256sum = getCurrentSha256sum(binaryName, shasumFile); - return new WrapperConfiguration(version, binaryName, expectedSha256sum); + const expectedSha256sums: string[] = [ + getCurrentSha256sum(binaryName, shasumFile), + ]; + + const supportedPlatforms = require(binaryDeploymentsFilePath); + const linuxstaticPlatform = supportedPlatforms['linuxstatic']; + if (linuxstaticPlatform) { + const archname = + os.arch() === 'x64' || os.arch() === 'amd64' ? 'amd64' : os.arch(); + const linuxstaticBinaryName = linuxstaticPlatform[archname]; + if (linuxstaticBinaryName && linuxstaticBinaryName !== binaryName) { + const linuxstaticShasum = getCurrentSha256sum( + linuxstaticBinaryName, + shasumFile, + ); + if (!linuxstaticShasum.startsWith('unknown-shasum-')) { + expectedSha256sums.push(linuxstaticShasum); + } + } + } + + return new WrapperConfiguration(version, binaryName, expectedSha256sums); } export function getCliArguments(inputArgv: string[]): string[] { @@ -255,7 +275,7 @@ export function formatErrorMessage(message: string): boolean { export function downloadExecutable( downloadUrl: string, filename: string, - filenameShasum: string, + filenameShasum: string[], ): Promise { return new Promise(function (resolve) { logErrorWithTimeStamps('Starting download'); @@ -263,6 +283,7 @@ export function downloadExecutable( const temp = path.join(__dirname, Date.now().toString()); const fileStream = fs.createWriteStream(temp); const shasum = createHash('sha256').setEncoding('hex'); + const expectedShasums = filenameShasum; const cleanupAfterError = (error: Error) => { try { @@ -283,9 +304,9 @@ export function downloadExecutable( 'Shasums:\n- actual: ' + actualShasum + '\n- expected: ' + - filenameShasum; + expectedShasums.join(' or '); - if (filenameShasum && actualShasum != filenameShasum) { + if (expectedShasums.some(Boolean) && !expectedShasums.includes(actualShasum)) { cleanupAfterError(Error('Shasum comparison failed!\n' + debugMessage)); } else { logErrorWithTimeStamps(debugMessage); @@ -343,7 +364,7 @@ export async function downloadWithBackup( downloadUrl: string, backupUrl: string, filename: string, - filenameShasum: string, + filenameShasum: string[], ): Promise { try { const error = await downloadExecutable( diff --git a/ts-binary-wrapper/test/unit/common.spec.ts b/ts-binary-wrapper/test/unit/common.spec.ts index 6468b1252c..42066043fd 100644 --- a/ts-binary-wrapper/test/unit/common.spec.ts +++ b/ts-binary-wrapper/test/unit/common.spec.ts @@ -143,7 +143,7 @@ describe('Configuration', () => { const config = new common.WrapperConfiguration( '1.2.3', 'snyk-win.exe', - '1234abcdef', + ['1234abcdef'], ); const actualDownloadLocation = config.getDownloadLocations().downloadUrl; @@ -213,7 +213,7 @@ describe('Testing binary bootstrapper', () => { it('downloadExecutable() succesfull', async () => { const binaryName = 'snyk-macos'; const shafileExtension = '.sha256'; - const config = new common.WrapperConfiguration('1.1080.0', binaryName, ''); + const config = new common.WrapperConfiguration('1.1080.0', binaryName, []); const shasumFile = config.getLocalLocation() + Math.random() + shafileExtension; @@ -221,7 +221,7 @@ describe('Testing binary bootstrapper', () => { const shasumDownload = await common.downloadExecutable( config.getDownloadLocations().downloadUrl + shafileExtension, shasumFile, - '', + [], ); expect(shasumDownload).toBeUndefined(); expect(fs.existsSync(shasumFile)).toBeTruthy(); @@ -232,7 +232,7 @@ describe('Testing binary bootstrapper', () => { const binaryDownload = await common.downloadExecutable( downloadUrl, config.getLocalLocation(), - expectedShasum, + [expectedShasum], ); expect(binaryDownload).toBeUndefined(); expect(fs.existsSync(config.getLocalLocation())).toBeTruthy(); @@ -255,7 +255,7 @@ describe('Testing binary bootstrapper', () => { it('downloadWithBackup() succesfull', async () => { const binaryName = 'snyk-macos'; const shafileExtension = '.sha256'; - const config = new common.WrapperConfiguration('1.1080.0', binaryName, ''); + const config = new common.WrapperConfiguration('1.1080.0', binaryName, []); const shasumFile = config.getLocalLocation() + Math.random() + shafileExtension; const { downloadUrl } = config.getDownloadLocations(); @@ -265,7 +265,7 @@ describe('Testing binary bootstrapper', () => { 'https://notdownloads.snyk.io/cli/v1.1080.0/snyk-macos.sha256', downloadUrl + shafileExtension, shasumFile, - '', + [], ); expect(shasumDownload).toBeUndefined(); expect(fs.existsSync(shasumFile)).toBeTruthy(); @@ -276,7 +276,7 @@ describe('Testing binary bootstrapper', () => { 'https://notdownloads.snyk.io/cli/v1.1080.0/snyk-macos', downloadUrl, config.getLocalLocation(), - expectedShasum, + [expectedShasum], ); expect(binaryDownload).toBeUndefined(); expect(fs.existsSync(config.getLocalLocation())).toBeTruthy(); @@ -300,7 +300,7 @@ describe('Testing binary bootstrapper', () => { it('downloadExecutable() fails due to incorrect shasum', async () => { const binaryName = 'snyk-macos'; const shafileExtension = '.sha256'; - const config = new common.WrapperConfiguration('1.1080.0', binaryName, ''); + const config = new common.WrapperConfiguration('1.1080.0', binaryName, []); const shasumFile = config.getLocalLocation() + Math.random() + shafileExtension; const { downloadUrl } = config.getDownloadLocations(); @@ -309,7 +309,7 @@ describe('Testing binary bootstrapper', () => { const shasumDownload = await common.downloadExecutable( downloadUrl + shafileExtension, shasumFile, - 'incorrect-shasum', + ['incorrect-shasum'], ); expect(shasumDownload?.message).toContain('Shasum comparison failed'); expect(fs.existsSync(shasumFile)).toBeFalsy(); @@ -318,7 +318,7 @@ describe('Testing binary bootstrapper', () => { it("downloadExecutable() try to download a file that doesn't exist", async () => { const binaryName = 'snyk-macos'; const shafileExtension = '.shoe256'; - const config = new common.WrapperConfiguration('1.1080.0', binaryName, ''); + const config = new common.WrapperConfiguration('1.1080.0', binaryName, []); const shasumFile = config.getLocalLocation() + Math.random() + shafileExtension; const { downloadUrl } = config.getDownloadLocations(); @@ -327,7 +327,7 @@ describe('Testing binary bootstrapper', () => { const shasumDownload = await common.downloadExecutable( downloadUrl + shafileExtension, shasumFile, - 'incorrect-shasum', + ['incorrect-shasum'], ); expect(shasumDownload?.message).toContain( 'Download failed! Server Response:', @@ -340,7 +340,7 @@ describe('Testing binary bootstrapper', () => { const shasumDownload = await common.downloadExecutable( 'https://notaurl', '', - '', + [], ); expect(shasumDownload).toBeDefined(); }); diff --git a/ts-binary-wrapper/test/util/prepareEnvironment.ts b/ts-binary-wrapper/test/util/prepareEnvironment.ts index 5b190bd4fd..c11cdb11ad 100644 --- a/ts-binary-wrapper/test/util/prepareEnvironment.ts +++ b/ts-binary-wrapper/test/util/prepareEnvironment.ts @@ -1,7 +1,15 @@ import * as common from '../../src/common'; import * as child_process from 'child_process'; import * as path from 'path'; -import { copyFileSync, mkdirSync, rmdirSync, writeFileSync } from 'fs'; +import { + appendFileSync, + copyFileSync, + mkdirSync, + readFileSync, + rmdirSync, + unlinkSync, + writeFileSync, +} from 'fs'; export class TestEnvironmentSetup { constructor( @@ -71,8 +79,25 @@ export class TestEnvironmentSetup { await common.downloadExecutable( 'https://downloads.snyk.io/cli/v' + version + '/sha256sums.txt.asc', shasumFile, - '', + [], ); + + const experimentalShasumFile = shasumFile + '.experimental'; + await common.downloadExecutable( + 'https://downloads.snyk.io/experimental/cli/v' + + version + + '/sha256sums.txt.asc', + experimentalShasumFile, + [], + ); + + const experimentalLines = readFileSync(experimentalShasumFile, 'utf8') + .split('\n') + .filter((line: string) => /^[a-zA-Z0-9]/.test(line)) + .map((line: string) => line.replace(/(\s+\*?)(\S+)$/, '$1experimental/$2')) + .join('\n'); + appendFileSync(shasumFile, '\n' + experimentalLines); + unlinkSync(experimentalShasumFile); } }