Skip to content

Commit

Permalink
Merge pull request #928 from snyk/chore/monitor-refactor
Browse files Browse the repository at this point in the history
chore: refactor shared code out and split out helper functions
  • Loading branch information
lili2311 committed Dec 30, 2019
2 parents 8a746e4 + b4e77c6 commit 87dc514
Show file tree
Hide file tree
Showing 11 changed files with 332 additions and 328 deletions.
14 changes: 14 additions & 0 deletions src/lib/monitor/count-total-deps-in-tree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { DepTree } from '../types';

export function countTotalDependenciesInTree(depTree: DepTree): number {
let count = 0;
if (depTree.dependencies) {
for (const name of Object.keys(depTree.dependencies)) {
const dep = depTree.dependencies[name];
if (dep) {
count += 1 + countTotalDependenciesInTree(dep);
}
}
}
return count;
}
15 changes: 15 additions & 0 deletions src/lib/monitor/drop-empty-deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { DepTree } from '../types';

export function dropEmptyDeps(depTree: DepTree) {
if (depTree.dependencies) {
const keys = Object.keys(depTree.dependencies);
if (keys.length === 0) {
delete depTree.dependencies;
} else {
for (const k of keys) {
dropEmptyDeps(depTree.dependencies[k]);
}
}
}
return depTree;
}
40 changes: 40 additions & 0 deletions src/lib/monitor/filter-out-missing-deps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { DepTree } from '../types';

interface FilteredDepTree {
filteredDepTree: DepTree;
missingDeps: string[];
}

export function filterOutMissingDeps(depTree: DepTree): FilteredDepTree {
const filteredDeps = {};
const missingDeps: string[] = [];

if (!depTree.dependencies) {
return {
filteredDepTree: depTree,
missingDeps,
};
}

for (const depKey of Object.keys(depTree.dependencies)) {
const dep = depTree.dependencies[depKey];
if (
(dep as any).missingLockFileEntry ||
((dep as any).labels && (dep as any).labels.missingLockFileEntry)
) {
// TODO(kyegupov): add field to the type
missingDeps.push(`${dep.name}@${dep.version}`);
} else {
filteredDeps[depKey] = dep;
}
}
const filteredDepTree: DepTree = {
...depTree,
dependencies: filteredDeps,
};

return {
filteredDepTree,
missingDeps,
};
}
148 changes: 20 additions & 128 deletions src/lib/monitor.ts → src/lib/monitor/index.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
import * as Debug from 'debug';
import * as depGraphLib from '@snyk/dep-graph';
import * as snyk from '../lib';
import { apiTokenExists } from './api-token';
import request = require('./request');
import * as config from './config';
import * as snyk from '..';
import { apiTokenExists } from '../api-token';
import request = require('../request');
import * as config from '../config';
import * as os from 'os';
import * as _ from 'lodash';
import { isCI } from './is-ci';
import * as analytics from './analytics';
import {
DepTree,
MonitorMeta,
MonitorResult,
MonitorOptions,
Options,
} from './types';
import * as projectMetadata from './project-metadata';
import { isCI } from '../is-ci';
import * as analytics from '../analytics';
import { DepTree, MonitorMeta, MonitorResult } from '../types';
import * as projectMetadata from '../project-metadata';
import * as path from 'path';
import {
MonitorError,
ConnectionTimeoutError,
AuthFailedError,
} from './errors';
import { countPathsToGraphRoot, pruneGraph } from './prune';
import { GRAPH_SUPPORTED_PACKAGE_MANAGERS } from './package-managers';
} from '../errors';
import { countPathsToGraphRoot, pruneGraph } from '../prune';
import { GRAPH_SUPPORTED_PACKAGE_MANAGERS } from '../package-managers';
import { legacyPlugin as pluginApi } from '@snyk/cli-interface';
import { isFeatureFlagSupportedForOrg } from './feature-flags';
import { isFeatureFlagSupportedForOrg } from '../feature-flags';
import { countTotalDependenciesInTree } from './count-total-deps-in-tree';
import { filterOutMissingDeps } from './filter-out-missing-deps';
import { dropEmptyDeps } from './drop-empty-deps';
import { pruneTree } from './prune-dep-tree';
import { pluckPolicies } from '../policy';

const debug = Debug('snyk');

Expand Down Expand Up @@ -59,93 +58,6 @@ interface Meta {
projectName: string;
}

interface FilteredDepTree {
filteredDepTree: DepTree;
missingDeps: string[];
}

function dropEmptyDeps(node: DepTree) {
if (node.dependencies) {
const keys = Object.keys(node.dependencies);
if (keys.length === 0) {
delete node.dependencies;
} else {
for (const k of keys) {
dropEmptyDeps(node.dependencies[k]);
}
}
}
}

function countTotalDependenciesInTree(depTree: DepTree): number {
let count = 0;
if (depTree.dependencies) {
for (const name of Object.keys(depTree.dependencies)) {
const dep = depTree.dependencies[name];
if (dep) {
count += 1 + countTotalDependenciesInTree(dep);
}
}
}
return count;
}

async function pruneTree(
tree: DepTree,
packageManagerName: string,
): Promise<DepTree> {
debug('pruning dep tree');
// Pruning requires conversion to the graph first.
// This is slow.
const graph = await depGraphLib.legacy.depTreeToGraph(
tree,
packageManagerName,
);
const prunedTree: DepTree = (await depGraphLib.legacy.graphToDepTree(
graph,
packageManagerName,
{ deduplicateWithinTopLevelDeps: true },
)) as DepTree;
// Transplant pruned dependencies in the original tree (we want to keep all other fields):
tree.dependencies = prunedTree.dependencies;
debug('finished pruning dep tree');
return tree;
}

function filterOutMissingDeps(depTree: DepTree): FilteredDepTree {
const filteredDeps = {};
const missingDeps: string[] = [];

if (!depTree.dependencies) {
return {
filteredDepTree: depTree,
missingDeps,
};
}

for (const depKey of Object.keys(depTree.dependencies)) {
const dep = depTree.dependencies[depKey];
if (
(dep as any).missingLockFileEntry ||
((dep as any).labels && (dep as any).labels.missingLockFileEntry)
) {
// TODO(kyegupov): add field to the type
missingDeps.push(`${dep.name}@${dep.version}`);
} else {
filteredDeps[depKey] = dep;
}
}
const filteredDepTree: DepTree = {
...depTree,
dependencies: filteredDeps,
};

return {
filteredDepTree,
missingDeps,
};
}

export async function monitor(
root: string,
meta: MonitorMeta,
Expand Down Expand Up @@ -189,7 +101,9 @@ export async function monitor(
prePruneDepCount = countTotalDependenciesInTree(info.package);
analytics.add('prePruneDepCount', prePruneDepCount);
debug('total dependencies: %d', prePruneDepCount);
debug('pruning dep tree');
pkg = await pruneTree(info.package, meta.packageManager);
debug('finished pruning dep tree');
}
if (['npm', 'yarn'].includes(meta.packageManager)) {
const { filteredDepTree, missingDeps } = filterOutMissingDeps(info.package);
Expand Down Expand Up @@ -217,7 +131,7 @@ export async function monitor(
analytics.add('targetBranch', target.branch);
}

dropEmptyDeps(pkg);
pkg = dropEmptyDeps(pkg);

// TODO(kyegupov): async/await
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -403,25 +317,3 @@ export async function monitorGraph(
);
});
}

function pluckPolicies(pkg) {
if (!pkg) {
return null;
}

if (pkg.snyk) {
return pkg.snyk;
}

if (!pkg.dependencies) {
return null;
}

return _.flatten(
Object.keys(pkg.dependencies)
.map((name) => {
return pluckPolicies(pkg.dependencies[name]);
})
.filter(Boolean),
);
}
22 changes: 22 additions & 0 deletions src/lib/monitor/prune-dep-tree.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as depGraphLib from '@snyk/dep-graph';
import { DepTree } from '../types';

export async function pruneTree(
tree: DepTree,
packageManagerName: string,
): Promise<DepTree> {
// Pruning requires conversion to the graph first.
// This is slow.
const graph = await depGraphLib.legacy.depTreeToGraph(
tree,
packageManagerName,
);
const prunedTree: DepTree = (await depGraphLib.legacy.graphToDepTree(
graph,
packageManagerName,
{ deduplicateWithinTopLevelDeps: true },
)) as DepTree;
// Transplant pruned dependencies in the original tree (we want to keep all other fields):
tree.dependencies = prunedTree.dependencies;
return tree;
}
79 changes: 79 additions & 0 deletions src/lib/plugins/get-deps-from-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as debugModule from 'debug';
import { legacyPlugin as pluginApi } from '@snyk/cli-interface';

import { find } from '../find-files';
import { Options, TestOptions } from '../types';
import { NoSupportedManifestsFoundError } from '../errors';
import { getMultiPluginResult } from './get-multi-plugin-result';
import { getSinglePluginResult } from './get-single-plugin-result';
import { detectPackageFile, AUTO_DETECTABLE_FILES } from '../detect';

const debug = debugModule('snyk');

// Force getDepsFromPlugin to return scannedProjects for processing
export async function getDepsFromPlugin(
root: string,
options: Options & TestOptions,
): Promise<pluginApi.MultiProjectResult> {
let inspectRes: pluginApi.InspectResult;

if (options.allProjects) {
// auto-detect only one-level deep for now
const targetFiles = await find(root, [], AUTO_DETECTABLE_FILES, 1);
debug(
`auto detect manifest files, found ${targetFiles.length}`,
targetFiles,
);
if (targetFiles.length === 0) {
throw NoSupportedManifestsFoundError([root]);
}
inspectRes = await getMultiPluginResult(root, options, targetFiles);
return inspectRes;
} else {
// TODO: is this needed for the auto detect handling above?
// don't override options.file if scanning multiple files at once
if (!options.scanAllUnmanaged) {
options.file = options.file || detectPackageFile(root);
}
if (!options.docker && !(options.file || options.packageManager)) {
throw NoSupportedManifestsFoundError([...root]);
}
inspectRes = await getSinglePluginResult(root, options);
}

if (!pluginApi.isMultiResult(inspectRes)) {
if (!inspectRes.package) {
// something went wrong if both are not present...
throw Error(
`error getting dependencies from ${options.packageManager} ` +
"plugin: neither 'package' nor 'scannedProjects' were found",
);
}
if (!inspectRes.package.targetFile && inspectRes.plugin) {
inspectRes.package.targetFile = inspectRes.plugin.targetFile;
}
// We are using "options" to store some information returned from plugin that we need to use later,
// but don't want to send to Registry in the Payload.
// TODO(kyegupov): decouple inspect and payload so that we don't need this hack
if (
inspectRes.plugin.meta &&
inspectRes.plugin.meta.allSubProjectNames &&
inspectRes.plugin.meta.allSubProjectNames.length > 1
) {
options.advertiseSubprojectsCount =
inspectRes.plugin.meta.allSubProjectNames.length;
}
return {
plugin: inspectRes.plugin,
scannedProjects: [{ depTree: inspectRes.package }],
};
} else {
// We are using "options" to store some information returned from plugin that we need to use later,
// but don't want to send to Registry in the Payload.
// TODO(kyegupov): decouple inspect and payload so that we don't need this hack
(options as any).projectNames = inspectRes.scannedProjects.map(
(scannedProject) => scannedProject.depTree.name,
);
return inspectRes;
}
}

0 comments on commit 87dc514

Please sign in to comment.