Skip to content

Commit

Permalink
feat: parameter all-sub-projects
Browse files Browse the repository at this point in the history
Co-authored-by: Or Sagie <or.sagie@gmail.com>
  • Loading branch information
Mila Votradovec and orsagie committed Apr 2, 2019
1 parent dfb2996 commit c3db7ca
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 8 deletions.
2 changes: 2 additions & 0 deletions help/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ Options:
--gradle-sub-project=<string>
For Gradle "multi project" configurations,
test a specific sub-project.
--all-sub-projects For "multi project" configurations, test all
sub-projects.
-q, --quiet ........ Silence all output.
-h, --help ......... This help information.
-v, --version ...... The CLI version.
Expand Down
7 changes: 7 additions & 0 deletions src/cli/commands/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ function monitor() {
snyk.id = options.id;
}


// This is a temporary check for gradual rollout of subprojects scanning
// TODO: delete once supported for monitor
if (options['all-sub-projects']) {
throw new Error('`--all-sub-projects` is currently not supported for `snyk monitor`');
}

return apiTokenExists('snyk monitor')
.then(function () {
return args.reduce(function (acc, path) {
Expand Down
6 changes: 4 additions & 2 deletions src/cli/commands/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,15 @@ async function test(...args) {
}
}

// add the tested path to the result of the test (or error)
// Not all test results are arrays in order to be backwards compatible
// with sctripts that use a callback with test. Coerce results/errors to be arrays
// and add the result options to each to be displayed
if (!Array.isArray(res)) {
res = [res];
}
for (const r of res) {
results.push(_.assign(r, {path}));
resultOptions.push(testOpts); // doesnt this put testOps in there too many times? line:53
resultOptions.push(testOpts);
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/lib/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,18 @@ export function loadPlugin(packageManager: string, options: types.Options = {}):
}
}
}

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;
}
}
}
1 change: 1 addition & 0 deletions src/lib/plugins/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface Options {
traverseNodeModules?: boolean;
dev?: boolean;
strictOutOfSync?: boolean | 'true' | 'false';
multiDepRoots?: boolean;
}

export interface Plugin {
Expand Down
21 changes: 16 additions & 5 deletions src/lib/snyk-test/run-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ interface DepRoot {

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

Expand Down Expand Up @@ -188,23 +188,34 @@ function assemblePayload(root: string, options, policyLocations: string[]): Prom
return assembleRemotePayload(root, options);
}

async function getDepsFromPlugin(root, options): Promise<Package> {
// Force getDepsFromPlugin to return depRoots for processing in assembleLocalPayload
interface MultiRootsPackage extends Package {
depRoots: DepRoot[];
}

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

if (!inspectRes.depRoots) {
if (!inspectRes.package) {
// something went wrong if both are not present...
throw Error('Error getting deps from plugin');
throw Error(`error getting dependencies from ${options.packageManager} ` +
'plugin: neither \'package\' nor \'depRoots\' were found');
}
if (!inspectRes.package.targetFile && inspectRes.plugin) {
inspectRes.package.targetFile = inspectRes.plugin.targetFile;
}
inspectRes.depRoots = [{depTree: inspectRes.package}];
}
return inspectRes;

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

async function assembleLocalPayload(root, options, policyLocations): Promise<Payload[]> {
Expand Down
142 changes: 141 additions & 1 deletion test/acceptance/cli.acceptance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ test('`test gradle-kotlin-dsl-app` returns correct meta', async (t) => {
return {package: {}};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
sinon.spy(plugin, 'inspect');
const loadPlugin = sinon.stub(plugins, 'loadPlugin');
t.teardown(loadPlugin.restore);
loadPlugin.withArgs('gradle').returns(plugin);
Expand Down Expand Up @@ -540,6 +540,7 @@ test('`test gradle-app` returns correct meta', async (t) => {

const res = await cli.test('gradle-app');
const meta = res.slice(res.indexOf('Organisation:')).split('\n');
t.false(spyPlugin.args[0][2].multiDepRoots, '`multiDepRoots` option is not sent');
t.match(meta[0], /Organisation:\s+test-org/, 'organisation displayed');
t.match(meta[1], /Package manager:\s+gradle/,
'package manager displayed');
Expand All @@ -550,6 +551,95 @@ test('`test gradle-app` returns correct meta', async (t) => {
'local policy not displayed');
});

test('`test gradle-app --all-sub-projects` returns sends `multiDepRoots` argument to plugin', async (t) => {
chdirWorkspaces();
const plugin = {
async inspect() {
return {plugin: {}, package: {}};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
const loadPlugin = sinon.stub(plugins, 'loadPlugin');
t.teardown(loadPlugin.restore);
loadPlugin.withArgs('gradle').returns(plugin);

const res = await cli.test('gradle-app', {
'all-sub-projects': true,
});
const meta = res.slice(res.indexOf('Organisation:')).split('\n');
t.true(spyPlugin.args[0][2].multiDepRoots);
});

test('`test gradle-app` plugin fails to return package or depRoots', async (t) => {
chdirWorkspaces();
const plugin = {
async inspect() {
return {plugin: {}};
},
};
sinon.spy(plugin, 'inspect');
const loadPlugin = sinon.stub(plugins, 'loadPlugin');
t.teardown(loadPlugin.restore);
loadPlugin.withArgs('gradle').returns(plugin);

try {
await cli.test('gradle-app', {});
t.fail('expected error');
} catch (error) {
t.match(error,
/error getting dependencies from gradle plugin: neither 'package' nor 'depRoots' were found/,
'error found');
}
});

test('`test gradle-app --all-sub-projects` returns correct multi tree meta', async (t) => {
chdirWorkspaces();
const plugin = {
async inspect() {
return {
plugin: {},
depRoots: [
{
depTree: {
name: 'tree1',
version: '1.0.0',
dependencies: {dep1: {name: 'dep1', version: '1'}},
},
}, {
depTree: {
name: 'tree2',
version: '2.0.0',
dependencies: {dep1: {name: 'dep2', version: '2'}},
},
},
],
};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
const loadPlugin = sinon.stub(plugins, 'loadPlugin');
t.teardown(loadPlugin.restore);
loadPlugin.withArgs('gradle').returns(plugin);

const res = await cli.test('gradle-app', {'all-sub-projects': true});
t.true(spyPlugin.args[0][2].multiDepRoots, '`multiDepRoots` option is sent');

const tests = res.split('Testing gradle-app...').filter((s) => !!s.trim());
t.equals(tests.length, 2, 'two projects tested independently');
t.match(res, /Tested 2 projects/, 'number projects tested displayed properly');
for (const tst of tests) {
const meta = tst.slice(tst.indexOf('Organisation:')).split('\n');
t.match(meta[0], /Organisation:\s+test-org/, 'organisation displayed');
t.match(meta[1], /Package manager:\s+gradle/,
'package manager displayed');
t.match(meta[2], /Target file:\s+build.gradle/, 'target file displayed');
t.match(meta[3], /Open source:\s+no/, 'open source displayed');
t.match(meta[4], /Project path:\s+gradle-app/, 'path displayed');
t.notMatch(meta[5], /Local Snyk policy:\s+found/,
'local policy not displayed');
}
});

test('`test` returns correct meta when target file specified', async (t) => {
chdirWorkspaces();
const res = await cli.test('ruby-app', {file: 'Gemfile.lock'});
Expand Down Expand Up @@ -2196,6 +2286,56 @@ test('`monitor pip-app --file=requirements.txt`', async (t) => {
}], 'calls python plugin');
});

test('`monitor gradle-app`', async (t) => {
chdirWorkspaces();
const plugin = {
async inspect() {
return {
plugin: {},
package: {},
};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
const loadPlugin = sinon.stub(plugins, 'loadPlugin');
t.teardown(loadPlugin.restore);
loadPlugin.withArgs('gradle').returns(plugin);

await cli.monitor('gradle-app');
const req = server.popRequest();
t.equal(req.method, 'PUT', 'makes PUT request');
t.match(req.url, '/monitor/gradle', 'puts at correct url');
t.same(spyPlugin.getCall(0).args,
['gradle-app', 'build.gradle', {
args: null,
}], 'calls gradle plugin');
});

test('`monitor gradle-app --all-sub-projects`', async (t) => {
t.plan(2);
chdirWorkspaces();
const plugin = {
async inspect() {
return {
plugin: {},
package: {},
};
},
};
const spyPlugin = sinon.spy(plugin, 'inspect');
const loadPlugin = sinon.stub(plugins, 'loadPlugin');
t.teardown(loadPlugin.restore);
loadPlugin.withArgs('gradle').returns(plugin);

try {
await cli.monitor('gradle-app', {'all-sub-projects': true});
} catch (e) {
t.contains(e, /not supported/);
}

t.true(spyPlugin.notCalled, "`inspect` method wasn't called");
});

test('`monitor golang-app --file=Gopkg.lock', async (t) => {
chdirWorkspaces();
const plugin = {
Expand Down

0 comments on commit c3db7ca

Please sign in to comment.