UNPKG

snyk-docker-plugin

Version:
141 lines 5.86 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.pipFilesToScannedProjects = void 0; const dep_graph_1 = require("@snyk/dep-graph"); const Debug = require("debug"); const event_loop_spinner_1 = require("event-loop-spinner"); const path = require("path"); const semver = require("semver"); const common_1 = require("../../../python-parser/common"); const metadata_parser_1 = require("../../../python-parser/metadata-parser"); const requirements_parser_1 = require("../../../python-parser/requirements-parser"); const debug = Debug("snyk"); class PythonDepGraphBuilder { constructor(name, requirements, metadata) { this.visitedMap = new Set(); this.requirements = requirements; this.metadata = metadata; this.builder = new dep_graph_1.DepGraphBuilder({ name: "pip" }, { name }); } async build() { for (const dep of this.requirements) { await this.addDependenciesToDepGraph(this.builder.rootNodeId, dep); } return this.builder.build(); } // depth-first search for dependencies and assigning them to the dep graph builder async addDependenciesToDepGraph(root, req) { var _a; if (event_loop_spinner_1.eventLoopSpinner.isStarving()) { await event_loop_spinner_1.eventLoopSpinner.spin(); } const metadata = this.findMetadata(req); if (!metadata) { return; } const extrasId = ((_a = req.extras) === null || _a === void 0 ? void 0 : _a.length) ? `:${req.extras}` : ""; const nodeId = `${metadata.name}@${metadata.version}${extrasId}`; if (!this.visitedMap.has(nodeId)) { this.visitedMap.add(nodeId); this.builder.addPkgNode({ name: metadata.name, version: metadata.version }, nodeId); for (const dep of metadata.dependencies) { if (this.shouldTraverse(req, dep)) { await this.addDependenciesToDepGraph(nodeId, dep); } } } this.builder.connectDep(root, nodeId); } // test extras and environment markers to determine whether a dependency is optional // if it is optional only traverse if the requirement asked for those optionals shouldTraverse(req, dep) { var _a; // always traverse deps with no extra environment markers (they're non-optional) if (!dep.extraEnvMarkers || dep.extraEnvMarkers.length === 0) { return true; } // determine if dep was required with extras, and those extras match the deps env markers const intersection = (_a = req.extras) === null || _a === void 0 ? void 0 : _a.filter((i) => { var _a; return (_a = dep.extraEnvMarkers) === null || _a === void 0 ? void 0 : _a.includes(i); }); // yes! this is an optional dependency that was asked for if (intersection && intersection.length > 0) { return true; } return false; // no! stop here we don't want to traverse optional dependencies } // find the best match for a dependency in found metadata files findMetadata(dep) { const nameMatches = this.metadata[dep.name.toLowerCase()]; if (!nameMatches || nameMatches.length === 0) { return null; } if (nameMatches.length === 1 || !dep.version) { return nameMatches[0]; } for (const meta of nameMatches) { if (semver.satisfies(meta.version, `${dep.specifier}${dep.version}`, true)) { return meta; } } // fallback to the first metadata file if no match is found return nameMatches[0]; } } /** * Creates a dep graph for every requirements.txt file that was found */ async function pipFilesToScannedProjects(filePathToContent) { const scanResults = []; const requirements = {}; const metadataItems = {}; for (const filepath of Object.keys(filePathToContent)) { const fileBaseName = path.basename(filepath); if (fileBaseName === "requirements.txt") { requirements[filepath] = (0, requirements_parser_1.getRequirements)(filePathToContent[filepath]); } else if (fileBaseName === "METADATA") { try { const packageInfo = (0, metadata_parser_1.getPackageInfo)(filePathToContent[filepath]); if (!metadataItems[packageInfo.name.toLowerCase()]) { metadataItems[packageInfo.name.toLowerCase()] = []; } metadataItems[packageInfo.name.toLowerCase()].push(packageInfo); } catch (err) { debug(err.message); } } } if (Object.keys(metadataItems).length === 0) { return scanResults; } // pre-sort each package name by version, descending for (const name of Object.keys(metadataItems)) { metadataItems[name].sort((v1, v2) => { return (0, common_1.compareVersions)(v1.version, v2.version); }); } for (const requirementsFile of Object.keys(requirements)) { if (requirements[requirementsFile].length === 0) { continue; } const builder = new PythonDepGraphBuilder(requirementsFile, requirements[requirementsFile], metadataItems); const depGraph = await builder.build(); if (!depGraph) { continue; } const depGraphFact = { type: "depGraph", data: depGraph, }; scanResults.push({ facts: [depGraphFact], identity: { type: "pip", targetFile: requirementsFile, }, }); } return scanResults; } exports.pipFilesToScannedProjects = pipFilesToScannedProjects; //# sourceMappingURL=pip.js.map