@soos-io/api-client
Version:
This is the SOOS API Client for registered clients leveraging the various integrations to the SOOS platform. Register for a free trial today at https://app.soos.io/register
662 lines (661 loc) • 35.3 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeneratedScanTypes = void 0;
const tslib_1 = require("tslib");
const enums_1 = require("./../enums");
const SOOSAnalysisApiClient_1 = tslib_1.__importDefault(require("../api/SOOSAnalysisApiClient"));
const SOOSProjectsApiClient_1 = tslib_1.__importDefault(require("../api/SOOSProjectsApiClient"));
const SOOSUserApiClient_1 = tslib_1.__importDefault(require("../api/SOOSUserApiClient"));
const constants_1 = require("../constants");
const enums_2 = require("../enums");
const logging_1 = require("../logging");
const utilities_1 = require("../utilities");
const FileSystem = tslib_1.__importStar(require("fs"));
const Path = tslib_1.__importStar(require("path"));
const form_data_1 = tslib_1.__importDefault(require("form-data"));
const Glob = tslib_1.__importStar(require("glob"));
const SOOSHooksApiClient_1 = tslib_1.__importDefault(require("../api/SOOSHooksApiClient"));
const SOOSAttributionApiClient_1 = tslib_1.__importDefault(require("../api/SOOSAttributionApiClient"));
const contributingDeveloperEnvironmentVariables = [
"Build_RequestedFor",
"Build_RequestedForEmail",
"CODEBUILD_BUILD_INITIATOR",
"CODEBUILD_INITIATOR",
"bamboo_planRepository_1_username",
"bamboo_ManualBuildTriggerReason_userName",
"BITBUCKET_STEP_TRIGGERER_UUID",
"CIRCLE_USERNAME",
"CIRCLE_PR_USERNAME",
"CI_COMMITTER_USERNAME",
"CI_COMMITTER_EMAIL",
"GITHUB_ACTOR",
"GITHUB_TRIGGERING_ACTOR",
"CHANGE_AUTHOR",
"CHANGE_AUTHOR_EMAIL",
"GIT_COMMITTER_NAME",
"GIT_COMMITTER_EMAIL",
"GIT_AUTHOR_NAME",
"GIT_AUTHOR_EMAIL",
"SOOS_CONTRIBUTING_DEVELOPER",
"TEAMCITY_BUILD_TRIGGEREDBY_USERNAME",
"TRAVIS_JOB_RESTARTED_BY",
"SOOS_CONTRIBUTING_DEVELOPER",
];
const GeneratedScanTypes = [enums_2.ScanType.CSA, enums_2.ScanType.SBOM, enums_2.ScanType.SCA];
exports.GeneratedScanTypes = GeneratedScanTypes;
const NoneColor = "\x1b[32m";
const InfoColor = "\x1b[34m";
const LowColor = "\x1b[90m";
const MediumColor = "\x1b[33m";
const HighColor = "\x1b[31m";
const CriticalColor = "\x1b[31m";
const ResetColor = "\x1b[0m";
class AnalysisService {
analysisApiClient;
attributionApiClient;
projectsApiClient;
userApiClient;
hooksApiClient;
constructor(analysisApiClient, attributionApiClient, projectsApiClient, userApiClient, hooksApiClient) {
this.analysisApiClient = analysisApiClient;
this.attributionApiClient = attributionApiClient;
this.projectsApiClient = projectsApiClient;
this.userApiClient = userApiClient;
this.hooksApiClient = hooksApiClient;
}
static create(apiKey, apiURL) {
const analysisApiClient = new SOOSAnalysisApiClient_1.default(apiKey, apiURL);
const attributionApiClient = new SOOSAttributionApiClient_1.default(apiKey, apiURL);
const projectsApiClient = new SOOSProjectsApiClient_1.default(apiKey, apiURL.replace("api.", "api-projects."));
const userApiClient = new SOOSUserApiClient_1.default(apiKey, apiURL.replace("api.", "api-user."));
const hooksApiClient = new SOOSHooksApiClient_1.default(apiKey, apiURL.replace("api.", "api-hooks."));
return new AnalysisService(analysisApiClient, attributionApiClient, projectsApiClient, userApiClient, hooksApiClient);
}
logStatusMessage(message) {
if (message) {
switch (message.severity) {
case enums_2.SeverityEnum.Unknown:
case enums_2.SeverityEnum.None:
case enums_2.SeverityEnum.Info:
case enums_2.SeverityEnum.Low:
logging_1.soosLogger.info(message.message);
break;
case enums_2.SeverityEnum.Medium:
case enums_2.SeverityEnum.High:
logging_1.soosLogger.warn(message.message);
break;
case enums_2.SeverityEnum.Critical:
logging_1.soosLogger.error(message.message);
break;
}
if (message.url) {
const linkText = message.linkText ? `[${message.linkText}]` : "";
logging_1.soosLogger.info(`${linkText}(${message.url})`);
}
}
}
async setupScan({ clientId, projectName, branchName, commitHash, buildVersion, buildUri, branchUri, integrationType, operatingEnvironment, integrationName, appVersion, scriptVersion, contributingDeveloperAudit, scanType, toolName, toolVersion, commandLine, scanMode, }) {
logging_1.soosLogger.info("Checking SOOS App status...");
const applicationStatus = await this.userApiClient.getApplicationStatus(clientId);
this.logStatusMessage(applicationStatus.statusMessage);
this.logStatusMessage(applicationStatus.clientMessage);
logging_1.soosLogger.logLineSeparator();
logging_1.soosLogger.info(`Creating scan for project '${projectName}'...`);
if (branchName) {
logging_1.soosLogger.info(`Branch Name: ${branchName}`);
}
if (contributingDeveloperAudit === undefined || contributingDeveloperAudit.length === 0) {
contributingDeveloperAudit = contributingDeveloperEnvironmentVariables
.map((ev) => {
const environmentVariableValue = process.env[ev];
return environmentVariableValue && environmentVariableValue.length > 0
? {
source: enums_2.ContributingDeveloperSource.EnvironmentVariable,
sourceName: ev,
contributingDeveloperId: environmentVariableValue,
}
: null;
})
.filter((a) => a !== null);
}
const result = await this.analysisApiClient.createScan({
clientId: clientId,
projectName: projectName,
commitHash: commitHash,
branch: branchName,
buildVersion: buildVersion,
buildUri: buildUri,
branchUri: branchUri,
integrationType: integrationType,
operatingEnvironment: operatingEnvironment,
integrationName: integrationName,
appVersion: appVersion,
scriptVersion: scriptVersion,
contributingDeveloperAudit: contributingDeveloperAudit,
scanType: scanType,
toolName: toolName,
toolVersion: toolVersion,
commandLine: commandLine,
scanMode: scanMode,
});
logging_1.soosLogger.info(`Project Hash: ${result.projectHash}`);
logging_1.soosLogger.info(`Branch Hash: ${result.branchHash}`);
logging_1.soosLogger.info(`Scan Id: ${result.analysisId}`);
logging_1.soosLogger.info("Scan created successfully.");
return result;
}
async startScan({ clientId, projectHash, analysisId, scanType, }) {
logging_1.soosLogger.info(`Starting ${scanType} Analysis Scan`);
await this.analysisApiClient.startScan({
clientId: clientId,
projectHash: projectHash,
analysisId: analysisId,
});
logging_1.soosLogger.info("Analysis scan started successfully... waiting for the scan");
}
async waitForScanToFinish({ scanStatusUrl, scanUrl, scanType, }) {
await (0, utilities_1.sleep)(constants_1.SOOS_CONSTANTS.Status.DelayTime);
const scanStatus = await this.analysisApiClient.getScanStatus({
scanStatusUrl: scanStatusUrl,
});
if (!scanStatus.isComplete) {
logging_1.soosLogger.info(`${utilities_1.StringUtilities.fromCamelToTitleCase(scanStatus.status)}...`);
return await this.waitForScanToFinish({ scanStatusUrl, scanUrl, scanType });
}
if (scanStatus.errors.length > 0) {
logging_1.soosLogger.group("Errors:");
logging_1.soosLogger.warn(JSON.stringify(scanStatus.errors, null, 2));
logging_1.soosLogger.groupEnd();
}
const output = this.getFinalScanStatusMessage(scanType, scanStatus, scanUrl, true);
logging_1.soosLogger.logLineSeparator();
output.map((o) => logging_1.soosLogger.always(o));
logging_1.soosLogger.logLineSeparator();
return scanStatus.status;
}
getColorBySeverity(severity, colorize) {
if (!severity) {
return "";
}
switch (severity.toLocaleLowerCase()) {
default:
case "unknown":
return "";
case "none":
return colorize ? NoneColor : "";
case "info":
return colorize ? InfoColor : "";
case "low":
return colorize ? LowColor : "";
case "medium":
return colorize ? MediumColor : "";
case "high":
return colorize ? HighColor : "";
case "critical":
return colorize ? CriticalColor : "";
}
}
getResetColor(colorize) {
return colorize ? ResetColor : "";
}
getFinalScanStatusMessage(scanType, scanStatus, scanUrl, colorize = false) {
const isGeneratedScanType = GeneratedScanTypes.includes(scanType);
const output = [];
output.push(`Scan ${scanStatus.isSuccess ? `${this.getColorBySeverity("none", colorize)}passed${this.getResetColor(colorize)}` : `${this.getColorBySeverity("high", colorize)}failed${this.getResetColor(colorize)}`}${scanStatus.isSuccess ? " with:" : " because of:"}`);
const maxLengthOfIssueText = 26;
const padChar = " ";
if (isGeneratedScanType) {
const vulnerabilityCount = scanStatus.issues?.Vulnerability?.count ?? 0;
output.push(`${utilities_1.StringUtilities.pluralizeWord(vulnerabilityCount, "Vulnerability:", "Vulnerabilities:").padEnd(maxLengthOfIssueText, padChar)}${this.getColorBySeverity(scanStatus.issues?.Vulnerability?.maxSeverity, colorize)}${vulnerabilityCount}${this.getResetColor(colorize)}`);
}
const violationCount = scanStatus.issues?.Violation?.count ?? 0;
output.push(`${utilities_1.StringUtilities.pluralizeWord(violationCount, "Violation:", "Violations:").padEnd(maxLengthOfIssueText, padChar)}${this.getColorBySeverity(scanStatus.issues?.Violation?.maxSeverity, colorize)}${violationCount}${this.getResetColor(colorize)}`);
if (scanType === enums_2.ScanType.DAST) {
const dastCount = scanStatus.issues?.Dast?.count ?? 0;
output.push(`${utilities_1.StringUtilities.pluralizeWord(dastCount, "Web Vulnerability:", "Web Vulnerabilities:").padEnd(maxLengthOfIssueText, padChar)}${this.getColorBySeverity(scanStatus.issues?.Dast?.maxSeverity, colorize)}${dastCount}${this.getResetColor(colorize)}`);
}
if (scanType === enums_2.ScanType.SAST) {
const sastCount = scanStatus.issues?.Sast?.count ?? 0;
output.push(`${utilities_1.StringUtilities.pluralizeWord(sastCount, "Code Issue:", "Code Issues:").padEnd(maxLengthOfIssueText, padChar)}${this.getColorBySeverity(scanStatus.issues?.Sast?.maxSeverity, colorize)}${sastCount}${this.getResetColor(colorize)}`);
}
if (isGeneratedScanType) {
const unknownPackageCount = scanStatus.issues?.UnknownPackage?.count ?? 0;
output.push(`${utilities_1.StringUtilities.pluralizeWord(unknownPackageCount, "Unknown Package:", "Unknown Packages:").padEnd(maxLengthOfIssueText, padChar)}${this.getColorBySeverity(scanStatus.issues?.UnknownPackage?.maxSeverity, colorize)}${unknownPackageCount}${this.getResetColor(colorize)}`);
const dependencyTypoCount = scanStatus.issues?.DependencyTypo?.count ?? 0;
output.push(`${utilities_1.StringUtilities.pluralizeWord(dependencyTypoCount, "Dependency Typo:", "Dependency Typos:").padEnd(maxLengthOfIssueText, padChar)}${this.getColorBySeverity(scanStatus.issues?.DependencyTypo?.maxSeverity, colorize)}${dependencyTypoCount}${this.getResetColor(colorize)}`);
const dependencySubstitutionCount = scanStatus.issues?.DependencySubstitution?.count ?? 0;
output.push(`${utilities_1.StringUtilities.pluralizeWord(dependencySubstitutionCount, "Dependency Substitution:", "Dependency Substitutions:").padEnd(maxLengthOfIssueText, padChar)}${this.getColorBySeverity(scanStatus.issues?.DependencySubstitution?.maxSeverity, colorize)}${dependencySubstitutionCount}${this.getResetColor(colorize)}`);
}
output.push(`Scan Report: ${scanUrl}`);
return output;
}
async waitForAttributionToFinish({ clientId, projectHash, branchHash, scanId, attributionId, }) {
const attributionStatus = await this.attributionApiClient.getAttributionStatus({
clientId,
projectHash,
branchHash,
scanId,
attributionId,
});
if (attributionStatus.status === enums_1.AttributionStatusEnum.Requested ||
attributionStatus.status === enums_1.AttributionStatusEnum.InProgress) {
logging_1.soosLogger.info(`Waiting for export to complete (${attributionStatus.status})...`);
await (0, utilities_1.sleep)(constants_1.SOOS_CONSTANTS.Status.DelayTime);
return await this.waitForAttributionToFinish({
clientId,
projectHash,
branchHash,
scanId,
attributionId,
});
}
if (attributionStatus.status === enums_1.AttributionStatusEnum.CompletedWithProblems ||
attributionStatus.status === enums_1.AttributionStatusEnum.Failed) {
logging_1.soosLogger.warn(JSON.stringify(attributionStatus.message));
}
return attributionStatus;
}
async generateFormattedOutput({ clientId, projectHash, projectName, branchHash, analysisId, format, fileType, includeDependentProjects, includeVulnerabilities, includeOriginalSbom, workingDirectory, }) {
logging_1.soosLogger.info(`Generating ${format} report as ${fileType} for ${projectName}...`);
const attributionStatus = await this.attributionApiClient.createAttributionRequest({
clientId: clientId,
projectHash: projectHash,
branchHash: branchHash,
scanId: analysisId,
format,
fileType,
includeDependentProjects,
includeVulnerabilities,
includeOriginalSbom,
});
const finalAttributionStatus = await this.waitForAttributionToFinish({
clientId,
projectHash,
branchHash,
scanId: analysisId,
attributionId: attributionStatus.id,
});
if (finalAttributionStatus.status === enums_1.AttributionStatusEnum.Completed ||
finalAttributionStatus.status === enums_1.AttributionStatusEnum.CompletedWithProblems) {
const output = await this.attributionApiClient.getScanAttribution({
clientId,
projectHash,
branchHash,
scanId: analysisId,
attributionId: finalAttributionStatus.id,
});
if (finalAttributionStatus.filename) {
logging_1.soosLogger.info(`${format} report generated successfully.`);
const outputFile = Path.join(workingDirectory, finalAttributionStatus.filename);
logging_1.soosLogger.info(`Writing ${format} report to ${outputFile}`);
FileSystem.writeFileSync(outputFile, output);
}
else {
logging_1.soosLogger.error(`${format} report was not generated. Verify a working directory was provided and try again.`);
}
}
else {
logging_1.soosLogger.error(`${format} report generation failed.`);
}
}
async updateScanStatus({ clientId, projectHash, branchHash, scanType, analysisId, status, message, scanStatusUrl, }) {
if (!(0, utilities_1.isNil)(scanStatusUrl)) {
const scanStatus = await this.analysisApiClient.getScanStatus({
scanStatusUrl: scanStatusUrl,
});
if (scanStatus.isComplete)
return;
}
await this.analysisApiClient.updateScanStatus({
clientId: clientId,
projectHash: projectHash,
branchHash: branchHash,
scanType: scanType,
scanId: analysisId,
status: status,
message: message,
});
if (status === enums_2.ScanStatus.Incomplete || status === enums_2.ScanStatus.Error)
logging_1.soosLogger.error(message);
}
async findAnalysisFiles(scanType, path, pattern, filesToExclude = null, directoriesToExclude = null, maxFiles = 0) {
process.chdir(path);
logging_1.soosLogger.info(`Searching for ${scanType} files from ${path}...`);
const files = Glob.sync(pattern, {
ignore: [
...(filesToExclude || []),
...(directoriesToExclude || []),
constants_1.SOOS_CONSTANTS.Files.SoosDirectoryExclusionGlobPattern,
],
nocase: true,
});
const matchingFiles = files.map((x) => Path.resolve(x));
logging_1.soosLogger.info(`${matchingFiles.length} files found matching pattern '${pattern}'.`);
matchingFiles.flat().map((filePath) => {
const filename = Path.basename(filePath);
const fileStats = FileSystem.statSync(filePath);
const fileSize = (0, utilities_1.formatBytes)(fileStats.size);
logging_1.soosLogger.info(`Found ${scanType} file '${filename}' (${fileSize}) at location '${filePath}'.`);
});
if (maxFiles < 1) {
return { filePaths: matchingFiles, hasMoreThanMaximumFiles: false };
}
const hasMoreThanMaximumFiles = matchingFiles.length > maxFiles;
const filesToUpload = matchingFiles.slice(0, maxFiles);
if (hasMoreThanMaximumFiles) {
const filesToSkip = matchingFiles.slice(maxFiles);
const filesDetectedString = utilities_1.StringUtilities.pluralizeTemplate(matchingFiles.length, "file was", "files were");
const filesSkippedString = utilities_1.StringUtilities.pluralizeTemplate(filesToSkip.length, "file");
logging_1.soosLogger.info(`The maximum number of ${scanType} files per scan is ${maxFiles}. ${filesDetectedString} detected, and ${filesSkippedString} will be not be uploaded. \n`, `The following files will not be included in the scan: \n`, filesToSkip.map((file) => ` "${Path.basename(file)}": "${file}"`).join("\n"));
}
return { filePaths: filesToUpload, hasMoreThanMaximumFiles };
}
async findManifestsAndHashableFiles({ clientId, projectHash, filesToExclude, directoriesToExclude, sourceCodePath, packageManagers, fileMatchType, }) {
const supportedScanFileFormats = await this.analysisApiClient.getSupportedScanFileFormats({
clientId: clientId,
});
const runFileHashing = fileMatchType === enums_1.FileMatchTypeEnum.FileHash ||
fileMatchType === enums_1.FileMatchTypeEnum.ManifestAndFileHash;
const runManifestMatching = fileMatchType === enums_1.FileMatchTypeEnum.Manifest ||
fileMatchType === enums_1.FileMatchTypeEnum.ManifestAndFileHash;
const filteredPackageManagers = packageManagers.length === 0
? supportedScanFileFormats
: supportedScanFileFormats.filter((packageManagerScanFileFormats) => packageManagers.some((pm) => utilities_1.StringUtilities.areEqual(pm, packageManagerScanFileFormats.packageManager, {
sensitivity: "base",
})));
const settings = await this.projectsApiClient.getProjectSettings({
clientId: clientId,
projectHash,
});
const manifestFormats = !runManifestMatching
? []
: filteredPackageManagers.flatMap((fpm) => {
return {
packageManager: fpm.packageManager,
manifests: fpm.manifests?.map((sm) => {
return {
isLockFile: sm.isLockFile,
includeWithLockFiles: sm.includeWithLockFiles,
supportsLockFiles: sm.SupportsLockFiles,
pattern: sm.pattern,
};
}) ?? [],
};
});
if (runManifestMatching) {
logging_1.soosLogger.debug(`Running manifest file matching for ${manifestFormats.length} manifest formats.`);
}
const manifestFiles = !runManifestMatching
? []
: this.searchForManifestFiles({
packageManagerManifests: manifestFormats,
useLockFile: settings.useLockFile ?? false,
filesToExclude,
directoriesToExclude,
sourceCodePath,
});
const archiveHashFormats = !runFileHashing
? []
: filteredPackageManagers.flatMap((fpm) => {
const hashableFiles = fpm.hashableFiles ?? [];
return !hashableFiles.some((hf) => hf.archiveFileExtensions)
? []
: {
packageManager: fpm.packageManager,
fileFormats: hashableFiles.map((hf) => {
return {
hashAlgorithms: hf.hashAlgorithms,
patterns: hf.archiveFileExtensions?.filter((afe) => !(0, utilities_1.isNil)(afe)) ?? [],
};
}) ?? [],
};
});
if (runFileHashing) {
logging_1.soosLogger.debug(`Running file hash matching for ${archiveHashFormats.length} archive file formats.`);
}
const archiveFileHashManifests = !runFileHashing || !archiveHashFormats.some((ahf) => ahf.fileFormats)
? []
: this.searchForHashableFiles({
hashableFileFormats: archiveHashFormats.filter((ahf) => ahf.fileFormats),
sourceCodePath,
filesToExclude,
directoriesToExclude,
});
const contentHashFormats = !runFileHashing
? []
: filteredPackageManagers.flatMap((fpm) => {
const hashableFiles = fpm.hashableFiles ?? [];
return !hashableFiles.some((hf) => hf.archiveContentFileExtensions)
? []
: {
packageManager: fpm.packageManager,
fileFormats: hashableFiles.map((hf) => {
return {
hashAlgorithms: hf.hashAlgorithms,
patterns: hf.archiveContentFileExtensions?.filter((afe) => !(0, utilities_1.isNil)(afe)) ?? [],
};
}) ?? [],
};
});
if (runFileHashing) {
logging_1.soosLogger.debug(`Running file hash matching for ${contentHashFormats.length} file formats.`);
}
const contentFileHashManifests = !runFileHashing || !contentHashFormats.some((chf) => chf.fileFormats)
? []
: this.searchForHashableFiles({
hashableFileFormats: contentHashFormats.filter((chf) => chf.fileFormats),
sourceCodePath,
filesToExclude,
directoriesToExclude,
});
const hashManifests = archiveFileHashManifests
.concat(contentFileHashManifests)
.filter((hm) => hm.fileHashes.length > 0);
if (runFileHashing && hashManifests) {
for (const soosHashesManifest of hashManifests) {
if (soosHashesManifest.fileHashes.length > 0) {
const hashManifestFileName = `${soosHashesManifest.packageManager}${constants_1.SOOS_CONSTANTS.SCA.SoosFileHashesManifest}`;
const hashManifestPath = Path.join(sourceCodePath, hashManifestFileName);
logging_1.soosLogger.info(`Generating SOOS hashes manifest: ${hashManifestPath}`);
FileSystem.writeFileSync(hashManifestPath, JSON.stringify(soosHashesManifest, null, 2));
manifestFiles.push({
packageManager: soosHashesManifest.packageManager,
name: hashManifestFileName,
path: hashManifestPath,
});
}
}
}
return { manifestFiles, hashManifests };
}
searchForManifestFiles({ packageManagerManifests, useLockFile, filesToExclude, directoriesToExclude, sourceCodePath, }) {
const currentDirectory = process.cwd();
logging_1.soosLogger.info(`Setting current directory to project path '${sourceCodePath}'.`);
process.chdir(sourceCodePath);
logging_1.soosLogger.info(`Lock file setting is ${useLockFile ? "on, ignoring non-lock files" : "off, ignoring lock files"}.`);
const manifestFiles = packageManagerManifests.reduce((accumulator, packageManagerManifests) => {
const matches = packageManagerManifests.manifests
.filter((manifest) => useLockFile === manifest.isLockFile ||
(useLockFile && !manifest.isLockFile && !manifest.supportsLockFiles) ||
(useLockFile && !manifest.isLockFile && manifest.includeWithLockFiles))
.map((manifest) => {
const manifestGlobPattern = manifest.pattern.startsWith(".")
? `*${manifest.pattern}`
: manifest.pattern;
const pattern = `**/${manifestGlobPattern}`;
const files = Glob.sync(pattern, {
ignore: [
...(filesToExclude || []),
...directoriesToExclude,
constants_1.SOOS_CONSTANTS.SCA.SoosPackageDirToExclude,
],
nocase: true,
});
const absolutePathFiles = files.map((x) => Path.resolve(x));
const matchingFilesMessage = `${absolutePathFiles.length} files found matching pattern '${pattern}'.`;
if (absolutePathFiles.length > 0) {
logging_1.soosLogger.info(matchingFilesMessage);
}
else {
logging_1.soosLogger.debug(matchingFilesMessage);
}
return absolutePathFiles;
});
return accumulator.concat(matches.flat().map((filePath) => {
const filename = Path.basename(filePath);
const fileStats = FileSystem.statSync(filePath);
const fileSize = (0, utilities_1.formatBytes)(fileStats.size);
logging_1.soosLogger.info(`Found manifest file '${filename}' (${fileSize}) at location '${filePath}'.`);
return {
packageManager: packageManagerManifests.packageManager,
name: filename,
path: filePath,
};
}));
}, []);
process.chdir(currentDirectory);
logging_1.soosLogger.info(`Setting current directory back to '${currentDirectory}'.\n`);
logging_1.soosLogger.info(`${manifestFiles.length} manifest files found.`);
return manifestFiles;
}
searchForHashableFiles({ hashableFileFormats, sourceCodePath, filesToExclude, directoriesToExclude, }) {
const currentDirectory = process.cwd();
logging_1.soosLogger.info(`Setting current directory to project path '${sourceCodePath}'.`);
process.chdir(sourceCodePath);
const fileHashes = hashableFileFormats.reduce((accumulator, fileFormatToHash) => {
const matches = fileFormatToHash.fileFormats.flatMap((fileFormat) => {
return fileFormat.patterns.flatMap((matchPattern) => {
const manifestGlobPattern = matchPattern.startsWith(".")
? `*${matchPattern}`
: matchPattern;
const pattern = `**/${manifestGlobPattern}`;
const files = Glob.sync(pattern, {
ignore: [
...(filesToExclude || []),
...directoriesToExclude,
constants_1.SOOS_CONSTANTS.SCA.SoosPackageDirToExclude,
],
nocase: true,
});
const absolutePathFiles = files.map((x) => Path.resolve(x));
const matchingFilesMessage = `${absolutePathFiles.length} files found matching pattern '${matchPattern}'.`;
if (absolutePathFiles.length > 0) {
logging_1.soosLogger.info(matchingFilesMessage);
}
else {
logging_1.soosLogger.debug(matchingFilesMessage);
}
return absolutePathFiles.flat().map((filePath) => {
const filename = Path.basename(filePath);
const fileDigests = fileFormat.hashAlgorithms.map((ha) => {
const digest = (0, utilities_1.generateFileHash)(ha.hashAlgorithm, ha.bufferEncoding, ha.digestEncoding, filePath);
logging_1.soosLogger.debug(`Found '${filePath}' (${ha.hashAlgorithm}:${digest})`);
return {
digest: digest,
hashAlgorithm: ha.hashAlgorithm,
};
});
return {
digests: fileDigests.map((d) => {
return {
hashAlgorithm: d.hashAlgorithm,
digest: d.digest,
};
}),
filename: filename,
path: filePath,
};
});
});
});
return accumulator.concat({
packageManager: fileFormatToHash.packageManager,
fileHashes: matches,
});
}, []);
process.chdir(currentDirectory);
logging_1.soosLogger.info(`Setting current directory back to '${currentDirectory}'.\n`);
return fileHashes;
}
async getAnalysisFilesAsFormData(analysisFilePaths, workingDirectory) {
const analysisFiles = analysisFilePaths.map((filePath) => {
return {
name: Path.basename(filePath),
path: filePath,
};
});
const formData = analysisFiles.reduce((formDataAcc, analysisFile, index) => {
const fileParts = analysisFile.path.replace(workingDirectory, "").split(Path.sep);
const parentFolder = fileParts.length >= 2 ? fileParts.slice(0, fileParts.length - 1).join(Path.sep) : "";
const suffix = index > 0 ? index : "";
const fileReadStream = FileSystem.createReadStream(analysisFile.path);
formDataAcc.append(`file${suffix}`, fileReadStream);
formDataAcc.append(`parentFolder${suffix}`, parentFolder);
return formDataAcc;
}, new form_data_1.default());
return formData;
}
async addManifestFilesToScan({ clientId, projectHash, branchHash, analysisId, scanType, scanStatusUrl, manifestFiles, }) {
const filesToUpload = manifestFiles.slice(0, constants_1.SOOS_CONSTANTS.FileUploads.MaxManifests);
const hasMoreThanMaximumManifests = manifestFiles.length > constants_1.SOOS_CONSTANTS.FileUploads.MaxManifests;
if (hasMoreThanMaximumManifests) {
const filesToSkip = manifestFiles.slice(constants_1.SOOS_CONSTANTS.FileUploads.MaxManifests);
const filesDetectedString = utilities_1.StringUtilities.pluralizeTemplate(manifestFiles.length, "file was", "files were");
const filesSkippedString = utilities_1.StringUtilities.pluralizeTemplate(filesToSkip.length, "file");
logging_1.soosLogger.info(`The maximum number of manifest per scan is ${constants_1.SOOS_CONSTANTS.FileUploads.MaxManifests}. ${filesDetectedString} detected, and ${filesSkippedString} will be not be uploaded. \n`, `The following manifests will not be included in the scan: \n`, filesToSkip.map((file) => ` "${file.name}": "${file.path}"`).join("\n"));
}
const manifestsByPackageManager = filesToUpload.reduce((accumulator, file) => {
const packageManagerFiles = accumulator[file.packageManager] ?? [];
return {
...accumulator,
[file.packageManager]: packageManagerFiles.concat(file),
};
}, {});
let allUploadsFailed = true;
for (const [packageManager, files] of Object.entries(manifestsByPackageManager)) {
try {
const manifestUploadResponse = await this.uploadManifestFiles({
clientId: clientId,
projectHash,
branchHash,
analysisId,
manifestFiles: files.map((f) => f.path),
hasMoreThanMaximumManifests,
});
logging_1.soosLogger.info(`${packageManager} Manifest Files: \n`, ` ${manifestUploadResponse.message} \n`, manifestUploadResponse.manifests
?.map((m) => ` ${m.name}: ${m.statusMessage}`)
.join("\n"));
allUploadsFailed = false;
}
catch (e) {
logging_1.soosLogger.warn(e instanceof Error ? e.message : e);
}
}
if (allUploadsFailed) {
await this.updateScanStatus({
clientId,
projectHash,
branchHash,
scanType,
analysisId: analysisId,
status: enums_2.ScanStatus.Incomplete,
message: `Error uploading manifests.`,
scanStatusUrl,
});
throw new Error("Error uploading manifests.");
}
}
async uploadManifestFiles({ clientId, projectHash, branchHash, analysisId, manifestFiles, hasMoreThanMaximumManifests, }) {
const formData = await this.getAnalysisFilesAsFormData(manifestFiles, process.cwd());
const response = await this.analysisApiClient.uploadManifestFiles({
clientId,
projectHash,
branchHash,
analysisId,
manifestFiles: formData,
hasMoreThanMaximumManifests,
});
return response;
}
}
exports.default = AnalysisService;
;