snyk-go-plugin
Version:
Snyk CLI Golang plugin
176 lines • 8.76 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildGraph = exports.buildDepGraphFromImportsAndModules = exports.getDepGraph = void 0;
const path = require("path");
const dep_graph_1 = require("@snyk/dep-graph");
const helpers_1 = require("./helpers");
const custom_error_1 = require("./errors/custom-error");
const version_1 = require("./version");
const sub_process_1 = require("./sub-process");
const package_url_1 = require("./package-url");
async function getDepGraph(root, targetFile, options = {}) {
var _a, _b;
const { args: additionalArgs = [], configuration } = options;
const includeGoStandardLibraryDeps = (_a = configuration === null || configuration === void 0 ? void 0 : configuration.includeGoStandardLibraryDeps) !== null && _a !== void 0 ? _a : false;
// Determine stdlib version
const stdlibVersion = includeGoStandardLibraryDeps
? await (0, helpers_1.resolveStdlibVersion)(root, targetFile)
: 'unknown';
const includePackageUrls = (_b = configuration === null || configuration === void 0 ? void 0 : configuration.includePackageUrls) !== null && _b !== void 0 ? _b : true;
return buildDepGraphFromImportsAndModules(root, targetFile, {
stdlibVersion,
additionalArgs,
includeGoStandardLibraryDeps,
includePackageUrls,
});
}
exports.getDepGraph = getDepGraph;
async function buildDepGraphFromImportsAndModules(root = '.', targetFile = 'go.mod', options = {}) {
var _a, _b;
// TODO(BST-657): parse go.mod file to obtain root module name and go version
const projectName = path.basename(root); // The correct name should come from the `go list` command
const projectVersion = '0.0.0'; // TODO(BST-657): try `git describe`?
options = {
stdlibVersion: 'unknown',
additionalArgs: [],
includeGoStandardLibraryDeps: false,
includePackageUrls: false,
...options,
};
let rootPkg = createPkgInfo(projectName, projectVersion, options);
let depGraphBuilder = new dep_graph_1.DepGraphBuilder({ name: 'gomodules' }, rootPkg);
let goDepsOutput;
const args = [
'list',
...((_a = options.additionalArgs) !== null && _a !== void 0 ? _a : []),
'-json',
'-deps',
'./...',
];
try {
const goModAbsolutPath = path.resolve(root, path.dirname(targetFile));
goDepsOutput = await (0, sub_process_1.runGo)(args, { cwd: goModAbsolutPath });
}
catch (err) {
if (/cannot find main module, but found/.test(err)) {
return depGraphBuilder.build();
}
if (/does not contain main module/.test(err)) {
return depGraphBuilder.build();
}
const userError = new custom_error_1.CustomError(err);
userError.userMessage = `'go ${args.join(' ')}' command failed with error: ${userError.message}`;
throw userError;
}
if (goDepsOutput.includes('matched no packages')) {
return depGraphBuilder.build();
}
const goDepsString = `[${goDepsOutput.replace(/}\r?\n{/g, '},{')}]`;
const goDeps = JSON.parse(goDepsString);
const packagesByName = {};
for (const gp of goDeps) {
packagesByName[gp.ImportPath] = gp; // ImportPath is the fully qualified name
}
const localPackages = goDeps.filter((gp) => !gp.DepOnly);
const localPackageWithMainModule = localPackages.find((localPackage) => !!(localPackage.Module && localPackage.Module.Main));
if ((_b = localPackageWithMainModule === null || localPackageWithMainModule === void 0 ? void 0 : localPackageWithMainModule.Module) === null || _b === void 0 ? void 0 : _b.Path) {
rootPkg = createPkgInfo(localPackageWithMainModule.Module.Path, projectVersion, options);
depGraphBuilder = new dep_graph_1.DepGraphBuilder({ name: 'gomodules' }, rootPkg);
}
const topLevelDeps = extractAllImports(localPackages);
const childrenChain = new Map();
const ancestorsChain = new Map();
buildGraph(depGraphBuilder, topLevelDeps, packagesByName, 'root-node', childrenChain, ancestorsChain, options);
return depGraphBuilder.build();
}
exports.buildDepGraphFromImportsAndModules = buildDepGraphFromImportsAndModules;
function buildGraph(depGraphBuilder, depPackages, packagesByName, currentParent, childrenChain, ancestorsChain, options, visited) {
var _a;
const depPackagesLen = depPackages.length;
for (let i = depPackagesLen - 1; i >= 0; i--) {
const localVisited = visited || new Set();
const packageImport = depPackages[i];
let version = 'unknown';
// ---------- Standard library handling ----------
if (isStandardLibraryPackage(packagesByName[packageImport])) {
if (!options.includeGoStandardLibraryDeps) {
continue; // skip when flag disabled
}
// All standard library packages are prefixed with "std/"
const stdPackageName = `std/${packageImport}`;
// create synthetic node and connect, then continue loop
const stdNode = createPkgInfo(stdPackageName, options.stdlibVersion || version, options);
depGraphBuilder.addPkgNode(stdNode, stdPackageName);
depGraphBuilder.connectDep(currentParent, stdPackageName);
continue;
}
// ---------- External package handling ----------
const pkgMeta = packagesByName[packageImport];
if (!pkgMeta || !pkgMeta.DepOnly) {
continue; // skip local or root-module packages
}
const pkg = pkgMeta;
const goModule = ((_a = pkg.Module) === null || _a === void 0 ? void 0 : _a.Replace) || pkg.Module;
if (goModule === null || goModule === void 0 ? void 0 : goModule.Version) {
// get hash (prefixed with #) or version (with v prefix removed)
version = (0, version_1.toSnykVersion)((0, version_1.parseVersion)(goModule.Version));
}
if (currentParent && packageImport) {
const newNode = createPkgInfo(packageImport, version, options, goModule);
const currentChildren = childrenChain.get(currentParent) || [];
const currentAncestors = ancestorsChain.get(currentParent) || [];
const isAncestorOrChild = currentChildren.includes(packageImport) ||
currentAncestors.includes(packageImport);
// @TODO boost: breaking cycles, re-work once dep-graph lib can handle cycles
if (packageImport === currentParent || isAncestorOrChild) {
continue;
}
if (localVisited.has(packageImport)) {
const prunedId = `${packageImport}:pruned`;
depGraphBuilder.addPkgNode(newNode, prunedId, {
labels: { pruned: 'true' },
});
depGraphBuilder.connectDep(currentParent, prunedId);
continue;
}
depGraphBuilder.addPkgNode(newNode, packageImport);
depGraphBuilder.connectDep(currentParent, packageImport);
localVisited.add(packageImport);
childrenChain.set(currentParent, [...currentChildren, packageImport]);
ancestorsChain.set(packageImport, [...currentAncestors, currentParent]);
const transitives = packagesByName[packageImport].Imports || [];
if (transitives.length > 0) {
buildGraph(depGraphBuilder, transitives, packagesByName, packageImport, childrenChain, ancestorsChain, options, localVisited);
}
}
}
}
exports.buildGraph = buildGraph;
function extractAllImports(goDeps) {
const goDepsImports = new Set();
for (const pkg of goDeps) {
if (pkg.Imports) {
for (const imp of pkg.Imports) {
goDepsImports.add(imp);
}
}
}
return Array.from(goDepsImports);
}
function isStandardLibraryPackage(pkgName) {
// Go Standard Library Packages are marked as Standard: true
return (pkgName === null || pkgName === void 0 ? void 0 : pkgName.Standard) === true;
}
function createPkgInfo(name, version, options, goModule) {
let purl;
if (options.includePackageUrls) {
purl = goModule
? // If we are dealing with a GoModule, the purl should be constructed from its values, because details can differ
// from `name` and `version`.
(0, package_url_1.createGoPurl)(goModule, name)
: // Otherwise create a simple purl that matches the `name` and `version` attributes.
(0, package_url_1.createGoPurl)({ Path: name, Version: version });
}
return { name, version, purl };
}
//# sourceMappingURL=dep-graph.js.map