Skip to content

Commit

Permalink
feat: suggests --all-sub-projects flag for multi-project gradle builds
Browse files Browse the repository at this point in the history
  • Loading branch information
Konstantin Yegupov committed Apr 30, 2019
1 parent a21cd0c commit af339ac
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 39 deletions.
17 changes: 15 additions & 2 deletions src/cli/commands/monitor.ts
Expand Up @@ -136,15 +136,21 @@ async function monitor(...args0: any[]): Promise<any> {
// a MultiDepRootsResult to an array of these.

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

// Post the project dependencies to the Registry
const monOutputs: string[] = [];
for (const depRootDeps of perDepRootResults) {
const res = await promiseOrCleanup(
snykMonitor(path, meta, depRootDeps),
Expand All @@ -171,6 +177,7 @@ async function monitor(...args0: any[]): Promise<any> {
manageUrl,
options,
subProjectName,
advertiseSubprojectsCount,
);
results.push({ok: true, data: monOutput, path, subProjectName});
}
Expand Down Expand Up @@ -223,13 +230,19 @@ async function monitor(...args0: any[]): Promise<any> {
throw new Error(output);
}

function formatMonitorOutput(packageManager, res, manageUrl, options, subProjectName?: string) {
function formatMonitorOutput(
packageManager, res, manageUrl, options, subProjectName?: string, advertiseSubprojectsCount?: number|null,
) {
const issues = res.licensesPolicy ? 'issues' : 'vulnerabilities';
const humanReadableName = subProjectName ? `${res.path} (${subProjectName})` : res.path;
let strOutput = chalk.bold.white('\nMonitoring ' + humanReadableName + '...\n\n') +
(packageManager === 'yarn' ?
'A yarn.lock file was detected - continuing as a Yarn project.\n' : '') +
'Explore this snapshot at ' + res.uri + '\n\n' +
(advertiseSubprojectsCount ?
chalk.bold.white(`This project has multiple sub-projects (${advertiseSubprojectsCount}), ` +
'use --all-sub-projects flag to scan all sub-projects.\n\n') :
'') +
(res.isMonitored ?
'Notifications about newly disclosed ' + issues + ' related ' +
'to these dependencies will be emailed to you.\n' :
Expand Down
17 changes: 13 additions & 4 deletions src/cli/commands/test.ts
Expand Up @@ -9,6 +9,7 @@ import {exists as apiTokenExists} from '../../lib/api-token';
import {SEVERITIES, WIZARD_SUPPORTED_PMS} from '../../lib/snyk-test/common';
import * as docker from '../../lib/docker-promotion';
import * as Debug from 'debug';
import { TestOptions } from '../../lib/types';

const debug = Debug('snyk');
const SEPARATOR = '\n-------------------------------------------------------\n';
Expand All @@ -20,7 +21,7 @@ const SEPARATOR = '\n-------------------------------------------------------\n';
async function test(...args) {
const resultOptions = [] as any[];
let results = [] as any[];
let options = {} as any;
let options = {} as any as TestOptions;

if (typeof args[args.length - 1] === 'object') {
options = args.pop();
Expand Down Expand Up @@ -191,7 +192,7 @@ function summariseErrorResults(errorResults) {
return '';
}

function displayResult(res, options) {
function displayResult(res, options: TestOptions) {
const meta = metaForDisplay(res, options) + '\n\n';
const dockerAdvice = dockerRemediationForDisplay(res);
const packageManager = options.packageManager;
Expand All @@ -216,6 +217,14 @@ function displayResult(res, options) {
dockerSuggestion += chalk.bold.white(docker.suggestionText);
}

let multiProjAdvice = '';

if (options.advertiseSubprojectsCount) {
multiProjAdvice = chalk.bold.white(
`This project has multiple sub-projects (${options.advertiseSubprojectsCount}), ` +
'use --all-sub-projects flag to scan all sub-projects.\n\n');
}

// OK => no vulns found, return
if (res.ok && res.vulnerabilities.length === 0) {
const vulnPathsText = options.showVulnPaths ?
Expand All @@ -229,7 +238,7 @@ function displayResult(res, options) {
'\n- Run `snyk test` as part of ' +
'your CI/test.';
return (
prefix + meta + summaryOKText + (
prefix + meta + summaryOKText + multiProjAdvice + (
isCI ? '' :
dockerAdvice +
nextStepsText +
Expand Down Expand Up @@ -300,7 +309,7 @@ function displayResult(res, options) {
groupedVulnInfoOutput.join('\n\n') + '\n\n\n' +
groupedDockerBinariesVulnInfoOutput.join('\n\n') + '\n\n' + meta + summary;

return prefix + body + dockerAdvice + dockerSuggestion;
return prefix + body + multiProjAdvice + dockerAdvice + dockerSuggestion;
}

function formatDockerBinariesIssues(dockerBinariesSortedGroupedVulns, binariesVulns, options) {
Expand Down
35 changes: 21 additions & 14 deletions src/lib/snyk-test/run-test.ts
Expand Up @@ -16,7 +16,7 @@ import spinner = require('../spinner');
import common = require('./common');
import gemfileLockToDependencies = require('../../lib/plugins/rubygems/gemfile-lock-to-dependencies');
import {convertTestDepGraphResultToLegacy, AnnotatedIssue} from './legacy';
import {Package, DepRoot} from '../types';
import {SingleDepRootResult, MultiDepRootsResult, isMultiResult, TestOptions} from '../types';

// tslint:disable-next-line:no-var-requires
const debug = require('debug')('snyk');
Expand Down Expand Up @@ -156,18 +156,15 @@ function assemblePayload(root: string, options, policyLocations: string[]): Prom
}

// Force getDepsFromPlugin to return depRoots for processing in assembleLocalPayload
interface MultiRootsPackage extends Package {
depRoots: DepRoot[];
}

async function getDepsFromPlugin(root, options): Promise<MultiRootsPackage> {
async function getDepsFromPlugin(root, options: TestOptions): Promise<MultiDepRootsResult> {
options.file = options.file || detect.detectPackageFile(root);
const plugin = plugins.loadPlugin(options.packageManager, options);
const moduleInfo = ModuleInfo(plugin, options.policy);
const pluginOptions = plugins.getPluginOptions(options.packageManager, options);
const inspectRes: Package = await moduleInfo.inspect(root, options.file, { ...options, ...pluginOptions });
const inspectRes: SingleDepRootResult | MultiDepRootsResult =
await moduleInfo.inspect(root, options.file, { ...options, ...pluginOptions });

if (!inspectRes.depRoots) {
if (!isMultiResult(inspectRes)) {
if (!inspectRes.package) {
// something went wrong if both are not present...
throw Error(`error getting dependencies from ${options.packageManager} ` +
Expand All @@ -176,15 +173,25 @@ async function getDepsFromPlugin(root, options): Promise<MultiRootsPackage> {
if (!inspectRes.package.targetFile && inspectRes.plugin) {
inspectRes.package.targetFile = inspectRes.plugin.targetFile;
}
inspectRes.depRoots = [{depTree: inspectRes.package}];
// 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.allDepRootNames
&& inspectRes.plugin.meta.allDepRootNames.length > 1) {
options.advertiseSubprojectsCount = inspectRes.plugin.meta.allDepRootNames.length;
}
return {
plugin: inspectRes.plugin,
depRoots: [{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);
return inspectRes;
}

return {
depRoots: inspectRes.depRoots,
plugin: inspectRes.plugin,
};
}

async function assembleLocalPayload(root, options, policyLocations): Promise<Payload[]> {
Expand Down
26 changes: 20 additions & 6 deletions src/lib/types.ts
Expand Up @@ -10,6 +10,9 @@ export interface PluginMetadata {
targetFile?: string; // this is wrong (because Shaun said it)
runtime?: any;
dockerImageId: any;
meta?: {
allDepRootNames: string[]; // To warn the user about subprojects not being scanned
};
}

export interface DepDict {
Expand All @@ -31,12 +34,6 @@ export interface DepRoot {
targetFile?: string;
}

export interface Package {
plugin: PluginMetadata;
depRoots?: DepRoot[]; // currently only returned by gradle
package?: DepTree;
}

// Legacy result type. Will be deprecated soon.
export interface SingleDepRootResult {
plugin: PluginMetadata;
Expand All @@ -57,3 +54,20 @@ export class MonitorError extends Error {
public code?: number;
public userMessage?: string;
}

export interface TestOptions {
org: string;
path: string;
docker?: boolean;
file?: string;
policy?: string;
json?: boolean;
'all-sub-projects'?: boolean; // Corresponds to multiDepRoot in plugins
'project-name'?: string;
'show-vulnerable-paths'?: string;
showVulnPaths?: boolean;
packageManager: string;
advertiseSubprojectsCount?: number;
subProjectNames?: string[];
severityThreshold?: string;
}
33 changes: 20 additions & 13 deletions test/acceptance/cli.acceptance.test.ts
Expand Up @@ -506,7 +506,7 @@ test('`test gradle-kotlin-dsl-app` returns correct meta', async (t) => {
chdirWorkspaces();
const plugin = {
async inspect() {
return {package: {}};
return {package: {}, plugin: {name: 'testplugin', runtime: 'testruntime'}};
},
};
sinon.spy(plugin, 'inspect');
Expand All @@ -530,7 +530,7 @@ test('`test gradle-app` returns correct meta', async (t) => {
chdirWorkspaces();
const plugin = {
async inspect() {
return {package: {}};
return {package: {}, plugin: {name: 'testplugin', runtime: 'testruntime'}};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
Expand Down Expand Up @@ -1140,7 +1140,7 @@ test('`test pip-app --file=requirements.txt`', async (t) => {
chdirWorkspaces();
const plugin = {
async inspect() {
return {package: {}};
return {package: {}, plugin: {name: 'testplugin', runtime: 'testruntime'}};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
Expand Down Expand Up @@ -1296,7 +1296,7 @@ test('`test nuget-app --file=project.json`', async (t) => {
chdirWorkspaces();
const plugin = {
async inspect() {
return {package: {}};
return {package: {}, plugin: {name: 'testplugin', runtime: 'testruntime'}};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
Expand Down Expand Up @@ -1371,7 +1371,7 @@ test('`test golang-app --file=Gopkg.lock`', async (t) => {
chdirWorkspaces();
const plugin = {
async inspect() {
return {package: {}};
return {package: {}, plugin: {name: 'testplugin', runtime: 'testruntime'}};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
Expand Down Expand Up @@ -1405,7 +1405,7 @@ test('`test golang-app --file=vendor/vendor.json`', async (t) => {
chdirWorkspaces();
const plugin = {
async inspect() {
return {package: {}};
return {package: {}, plugin: {name: 'testplugin', runtime: 'testruntime'}};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
Expand Down Expand Up @@ -1439,7 +1439,7 @@ test('`test golang-app` auto-detects golang/dep', async (t) => {
chdirWorkspaces();
const plugin = {
async inspect() {
return {package: {}};
return {package: {}, plugin: {name: 'testplugin', runtime: 'testruntime'}};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
Expand Down Expand Up @@ -1471,7 +1471,7 @@ test('`test golang-app-govendor` auto-detects govendor', async (t) => {
chdirWorkspaces();
const plugin = {
async inspect() {
return {package: {}};
return {package: {}, plugin: {name: 'testplugin', runtime: 'testruntime'}};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
Expand Down Expand Up @@ -1502,7 +1502,7 @@ test('`test composer-app --file=composer.lock`', async (t) => {
chdirWorkspaces();
const plugin = {
async inspect() {
return {package: {}};
return {package: {}, plugin: {name: 'testplugin', runtime: 'testruntime'}};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
Expand Down Expand Up @@ -1535,7 +1535,7 @@ test('`test composer-app` auto-detects composer.lock', async (t) => {
chdirWorkspaces();
const plugin = {
async inspect() {
return {package: {}};
return {package: {}, plugin: {name: 'testplugin', runtime: 'testruntime'}};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
Expand Down Expand Up @@ -1566,7 +1566,7 @@ test('`test composer-app golang-app nuget-app` auto-detects all three projects',
chdirWorkspaces();
const plugin = {
async inspect() {
return {package: {}};
return {package: {}, plugin: {name: 'testplugin', runtime: 'testruntime'}};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
Expand Down Expand Up @@ -2292,7 +2292,13 @@ test('`monitor gradle-app`', async (t) => {
const plugin = {
async inspect() {
return {
plugin: {},
plugin: {
name: 'testplugin',
runtime: 'testruntime',
meta: {
allDepRootNames: ['foo', 'bar'],
},
},
package: {},
};
},
Expand All @@ -2302,7 +2308,8 @@ test('`monitor gradle-app`', async (t) => {
t.teardown(loadPlugin.restore);
loadPlugin.withArgs('gradle').returns(plugin);

await cli.monitor('gradle-app');
let output = await cli.monitor('gradle-app');
t.match(output, /use --all-sub-projects flag to scan all sub-projects/, "all-sub-projects flag is suggested");
const req = server.popRequest();
t.equal(req.method, 'PUT', 'makes PUT request');
t.match(req.url, '/monitor/gradle', 'puts at correct url');
Expand Down

0 comments on commit af339ac

Please sign in to comment.