Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: reinstate --all-sub-project support for Gradle #732

Merged
merged 1 commit into from
Aug 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 18 additions & 21 deletions src/cli/commands/monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ import * as detect from '../../lib/detect';
import * as plugins from '../../lib/plugins';
import {ModuleInfo} from '../../lib/module-info'; // TODO(kyegupov): fix import
import {
SingleDepRootResult,
MultiDepRootsResult,
isMultiResult,
MonitorOptions,
MonitorMeta,
MonitorResult,
Expand All @@ -30,6 +27,7 @@ import {
MonitorError,
UnsupportedFeatureFlagError,
} from '../../lib/errors';
import { legacyPlugin as pluginApi } from '@snyk/cli-interface';

const SEPARATOR = '\n-------------------------------------------------------\n';

Expand Down Expand Up @@ -78,7 +76,7 @@ async function monitor(...args0: MethodArgs): Promise<any> {
snyk.id = options.id;
}

if (options['all-sub-projects'] && options['project-name']) {
if (options.allSubProjects && options['project-name']) {
throw new Error('`--all-sub-projects` is currently not compatible with `--project-name`');
}

Expand Down Expand Up @@ -126,13 +124,12 @@ async function monitor(...args0: MethodArgs): Promise<any> {

// Scan the project dependencies via a plugin

const pluginOptions = plugins.getPluginOptions(packageManager, options);
analytics.add('packageManager', packageManager);
analytics.add('pluginOptions', pluginOptions);
analytics.add('pluginOptions', options);

// TODO: the type should depend on multiDepRoots flag
const inspectResult: SingleDepRootResult|MultiDepRootsResult = await promiseOrCleanup(
moduleInfo.inspect(path, targetFile, { ...options, ...pluginOptions }),
// TODO: the type should depend on allSubProjects flag
const inspectResult: pluginApi.InspectResult = await promiseOrCleanup(
moduleInfo.inspect(path, targetFile, { ...options }),
spinner.clear(analyzingDepsSpinnerLabel));

analytics.add('pluginName', inspectResult.plugin.name);
Expand All @@ -155,30 +152,30 @@ async function monitor(...args0: MethodArgs): Promise<any> {

// We send results from "all-sub-projects" scanning as different Monitor objects

// SingleDepRootResult is a legacy format understood by Registry, so we have to convert
// a MultiDepRootsResult to an array of these.
// SinglePackageResult is a legacy format understood by Registry, so we have to convert
// a MultiProjectResult to an array of these.

let perDepRootResults: SingleDepRootResult[] = [];
let perSubProjectResults: pluginApi.SinglePackageResult[] = [];
let advertiseSubprojectsCount: number | null = null;
if (isMultiResult(inspectResult)) {
perDepRootResults = inspectResult.depRoots.map(
(depRoot) => ({plugin: inspectResult.plugin, package: depRoot.depTree}));
if (pluginApi.isMultiResult(inspectResult)) {
perSubProjectResults = inspectResult.scannedProjects.map(
(scannedProject) => ({plugin: inspectResult.plugin, package: scannedProject.depTree}));
} else {
if (!options['gradle-sub-project']
&& inspectResult.plugin.meta
&& inspectResult.plugin.meta.allSubProjectNames
&& inspectResult.plugin.meta.allSubProjectNames.length > 1) {
advertiseSubprojectsCount = inspectResult.plugin.meta.allSubProjectNames.length;
}
perDepRootResults = [inspectResult];
perSubProjectResults = [inspectResult];
}

// Post the project dependencies to the Registry
for (const depRootDeps of perDepRootResults) {
maybePrintDeps(options, depRootDeps.package);
for (const subProjDeps of perSubProjectResults) {
maybePrintDeps(options, subProjDeps.package);

const res = await promiseOrCleanup(
snykMonitor(path, meta, depRootDeps, targetFile),
snykMonitor(path, meta, subProjDeps, targetFile),
spinner.clear(postingMonitorSpinnerLabel));

await spinner.clear(postingMonitorSpinnerLabel)(res);
Expand All @@ -193,8 +190,8 @@ async function monitor(...args0: MethodArgs): Promise<any> {
const manageUrl = url.format(endpoint);

endpoint.pathname = leader + '/monitor/' + res.id;
const subProjectName = isMultiResult(inspectResult)
? depRootDeps.package.name
const subProjectName = pluginApi.isMultiResult(inspectResult)
? subProjDeps.package.name
: undefined;
const monOutput = formatMonitorOutput(
packageManager,
Expand Down
4 changes: 2 additions & 2 deletions src/lib/module-info/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as _ from 'lodash';
import * as Debug from 'debug';
import { SingleDepRootResult } from '../types';
import { legacyPlugin as pluginApi } from '@snyk/cli-interface';

const debug = Debug('snyk-module-info');

export function ModuleInfo(plugin, policy) {
return {
async inspect(root, targetFile, options): Promise<SingleDepRootResult> {
async inspect(root, targetFile, options): Promise<pluginApi.SinglePackageResult> {
const pluginOptions = _.merge({
args: options._doubleDashArgs,
}, options);
Expand Down
9 changes: 5 additions & 4 deletions src/lib/monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import * as os from 'os';
import * as _ from 'lodash';
import {isCI} from './is-ci';
import * as analytics from './analytics';
import { SingleDepRootResult, DepTree, MonitorMeta, MonitorResult } from './types';
import { DepTree, MonitorMeta, MonitorResult } from './types';
import * as projectMetadata from './project-metadata';
import * as path from 'path';
import {MonitorError, ConnectionTimeoutError} from './errors';
import { countPathsToGraphRoot, pruneGraph } from './prune';
import { GRAPH_SUPPORTED_PACKAGE_MANAGERS } from './package-managers';
import { legacyPlugin as pluginApi } from '@snyk/cli-interface';

const debug = Debug('snyk');

Expand Down Expand Up @@ -104,7 +105,7 @@ function filterOutMissingDeps(depTree: DepTree): FilteredDepTree {

for (const depKey of Object.keys(depTree.dependencies)) {
const dep = depTree.dependencies[depKey];
if (dep.missingLockFileEntry) {
if ((dep as any).missingLockFileEntry) { // TODO(kyegupov): add field to the type
missingDeps.push(`${dep.name}@${dep.version}`);
} else {
filteredDeps[depKey] = dep;
Expand All @@ -124,7 +125,7 @@ function filterOutMissingDeps(depTree: DepTree): FilteredDepTree {
export async function monitor(
root: string,
meta: MonitorMeta,
info: SingleDepRootResult,
info: pluginApi.SinglePackageResult,
targetFile?: string,
): Promise<MonitorResult> {
apiTokenExists();
Expand Down Expand Up @@ -231,7 +232,7 @@ export async function monitor(
export async function monitorGraph(
root: string,
meta: MonitorMeta,
info: SingleDepRootResult,
info: pluginApi.SinglePackageResult,
targetFile?: string,
): Promise<MonitorResult> {
const packageManager = meta.packageManager;
Expand Down
15 changes: 0 additions & 15 deletions src/lib/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,3 @@ export function loadPlugin(packageManager: SupportedPackageManagers,
}
}
}

export function getPluginOptions(packageManager: string, options: types.Options): types.Options {
const pluginOptions: types.Options = {};
switch (packageManager) {
case 'gradle': {
if (options['all-sub-projects']) {
pluginOptions.multiDepRoots = true;
}
return pluginOptions;
}
default: {
return pluginOptions;
}
}
}
4 changes: 2 additions & 2 deletions src/lib/plugins/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export interface InspectResult {
runtime?: string;
};
package?: any;
depRoots?: any;
scannedProjects?: any;
}

export interface Options {
Expand All @@ -13,7 +13,7 @@ export interface Options {
traverseNodeModules?: boolean;
dev?: boolean;
strictOutOfSync?: boolean | 'true' | 'false';
multiDepRoots?: boolean;
allSubProjects?: boolean;
debug?: boolean;
packageManager?: string;
composerIsFine?: boolean;
Expand Down
5 changes: 3 additions & 2 deletions src/lib/print-deps.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { DepDict, Options, MonitorOptions, DepTree } from './types';
import { DepDict, Options, MonitorOptions } from './types';
import { legacyCommon as legacyApi } from '@snyk/cli-interface';

// This option is still experimental and might be deprecated.
// It might be a better idea to convert it to a command (i.e. do not perform test/monitor).
export function maybePrintDeps(options: Options | MonitorOptions, rootPackage: DepTree) {
export function maybePrintDeps(options: Options | MonitorOptions, rootPackage: legacyApi.DepTree) {
if (options['print-deps']) {
if (options.json) {
// Will produce 2 JSON outputs, one for the deps, one for the vuln scan.
Expand Down
31 changes: 17 additions & 14 deletions src/lib/snyk-test/run-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import common = require('./common');
import {DepTree, TestOptions} from '../types';
import gemfileLockToDependencies = require('../../lib/plugins/rubygems/gemfile-lock-to-dependencies');
import {convertTestDepGraphResultToLegacy, AnnotatedIssue, LegacyVulnApiResult, TestDepGraphResponse} from './legacy';
import {SingleDepRootResult, MultiDepRootsResult, isMultiResult, Options} from '../types';
import {Options} from '../types';
import {
NoSupportedManifestsFoundError,
InternalServerError,
Expand All @@ -26,6 +26,7 @@ import {
import { maybePrintDeps } from '../print-deps';
import { SupportedPackageManagers } from '../package-managers';
import { countPathsToGraphRoot, pruneGraph } from '../prune';
import { legacyPlugin as pluginApi } from '@snyk/cli-interface';

// tslint:disable-next-line:no-var-requires
const debug = require('debug')('snyk');
Expand Down Expand Up @@ -207,23 +208,22 @@ function assemblePayloads(root: string, options: Options & TestOptions): Promise
return assembleRemotePayloads(root, options);
}

// Force getDepsFromPlugin to return depRoots for processing in assembleLocalPayload
async function getDepsFromPlugin(root, options: Options): Promise<MultiDepRootsResult> {
// Force getDepsFromPlugin to return scannedProjects for processing in assembleLocalPayload
async function getDepsFromPlugin(root, options: Options): Promise<pluginApi.MultiProjectResult> {
options.file = options.file || detect.detectPackageFile(root);
if (!options.docker && !(options.file || options.packageManager)) {
throw NoSupportedManifestsFoundError([...root]);
}
const plugin = plugins.loadPlugin(options.packageManager, options);
const moduleInfo = ModuleInfo(plugin, options.policy);
const pluginOptions = plugins.getPluginOptions(options.packageManager, options);
const inspectRes: SingleDepRootResult | MultiDepRootsResult =
await moduleInfo.inspect(root, options.file, { ...options, ...pluginOptions });
const inspectRes: pluginApi.InspectResult =
await moduleInfo.inspect(root, options.file, { ...options });

if (!isMultiResult(inspectRes)) {
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 \'depRoots\' were found');
'plugin: neither \'package\' nor \'scannedProjects\' were found');
}
if (!inspectRes.package.targetFile && inspectRes.plugin) {
inspectRes.package.targetFile = inspectRes.plugin.targetFile;
Expand All @@ -238,13 +238,13 @@ async function getDepsFromPlugin(root, options: Options): Promise<MultiDepRootsR
}
return {
plugin: inspectRes.plugin,
depRoots: [{depTree: inspectRes.package}],
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.subProjectNames = inspectRes.depRoots.map((depRoot) => depRoot.depTree.name);
(options as any).subProjectNames = inspectRes.scannedProjects.map((scannedProject) => scannedProject.depTree.name);
return inspectRes;
}
}
Expand All @@ -263,14 +263,14 @@ async function assembleLocalPayloads(root, options: Options & TestOptions): Prom
const deps = await getDepsFromPlugin(root, options);
analytics.add('pluginName', deps.plugin.name);

for (const depRoot of deps.depRoots) {
const pkg = depRoot.depTree;
for (const scannedProject of deps.scannedProjects) {
const pkg = scannedProject.depTree;
if (options['print-deps']) {
await spinner.clear<void>(spinnerLbl)();
maybePrintDeps(options, pkg);
}
if (deps.plugin && deps.plugin.packageManager) {
options.packageManager = deps.plugin.packageManager;
(options as any).packageManager = deps.plugin.packageManager;
}

if (pkg.docker) {
Expand Down Expand Up @@ -332,7 +332,10 @@ async function assembleLocalPayloads(root, options: Options & TestOptions): Prom
} else {
// Graphs are more compact and robust representations.
// Legacy parts of the code are still using trees, but will eventually be fully migrated.
debug('converting dep-tree to dep-graph', {name: pkg.name, targetFile: depRoot.targetFile || options.file});
debug('converting dep-tree to dep-graph', {
name: pkg.name,
targetFile: scannedProject.targetFile || options.file,
});
let depGraph = await depGraphLib.legacy.depTreeToGraph(
pkg, options.packageManager);

Expand Down
47 changes: 4 additions & 43 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { SupportedPackageManagers } from './package-managers';

// TODO(kyegupov): use a shared repository snyk-cli-interface
import { legacyCommon as legacyApi } from '@snyk/cli-interface';

export interface PluginMetadata {
name: string;
Expand All @@ -19,45 +18,7 @@ export interface DepDict {
[name: string]: DepTree;
}

export interface DepTree {
name: string;
version: string;
dependencies?: DepDict;
packageFormatVersion?: string;
docker?: any;
files?: any;
targetFile?: string;
missingLockFileEntry?: boolean;

labels?: {
[key: string]: string;

// Known keys:
// pruned: identical subtree already presents in the parent node.
// See --prune-repeated-subdependencies flag.
};
}

export interface DepRoot {
depTree: DepTree; // to be soon replaced with depGraph
targetFile?: string;
}

// Legacy result type. Will be deprecated soon.
export interface SingleDepRootResult {
plugin: PluginMetadata;
package: DepTree;
}

export interface MultiDepRootsResult {
plugin: PluginMetadata;
depRoots: DepRoot[];
}

// https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards
export function isMultiResult(pet: SingleDepRootResult | MultiDepRootsResult): pet is MultiDepRootsResult {
return !!(pet as MultiDepRootsResult).depRoots;
}
export type DepTree = legacyApi.DepTree;

export interface TestOptions {
traverseNodeModules: boolean;
Expand All @@ -81,7 +42,7 @@ export interface Options {
'ignore-policy'?: boolean;
'trust-policies'?: boolean; // used in snyk/policy lib
'policy-path'?: boolean;
'all-sub-projects'?: boolean; // Corresponds to multiDepRoot in plugins
allSubProjects?: boolean;
'project-name'?: string;
'show-vulnerable-paths'?: string;
showVulnPaths?: boolean;
Expand All @@ -100,7 +61,7 @@ export interface MonitorOptions {
file?: string;
policy?: string;
json?: boolean;
'all-sub-projects'?: boolean; // Corresponds to multiDepRoot in plugins
allSubProjects?: boolean;
'project-name'?: string;
'print-deps'?: boolean;
'experimental-dep-graph'?: boolean;
Expand Down
Loading