snyk-docker-plugin
Version:
Snyk CLI docker plugin
141 lines • 5.86 kB
JavaScript
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
;