Skip to content

Commit

Permalink
feat: include command "container" as alias for option "--docker"
Browse files Browse the repository at this point in the history
 This is the first step to create a "snyk container test" or "snyk container monitor" commands that doesn't require Docker engine running. Although it's not relevant to all users, more flexibility here is both more technically correct, future proofs our technology, and is important to several separate strategic partners.

 The option "--docker" is still available and the CLI is still compatible with this option. Future documentation improvement will give preference for the new command "container" rather than the option "--docker".
  • Loading branch information
Arthur Granado committed May 19, 2020
1 parent 505720d commit 6bc0085
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/cli/args.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as abbrev from 'abbrev';

import debugModule = require('debug');
import { parseMode } from './modes';

export declare interface Global extends NodeJS.Global {
ignoreUnknownCA: boolean;
Expand Down Expand Up @@ -111,6 +112,9 @@ export function args(rawArgv: string[]): Args {
// an argument to our command, like `snyk help protect`
let command = argv._.shift() as string; // can actually be undefined

// snyk [mode?] [command] [paths?] [options-double-dash]
command = parseMode(command, argv);

// alias switcheroo - allows us to have
if (cli.aliases[command]) {
command = cli.aliases[command];
Expand Down
3 changes: 3 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '../lib/errors';
import stripAnsi from 'strip-ansi';
import { ExcludeFlagInvalidInputError } from '../lib/errors/exclude-flag-invalid-input';
import { modeValidation } from './modes';

const debug = Debug('snyk');
const EXIT_CODES = {
Expand Down Expand Up @@ -158,6 +159,8 @@ async function main() {
let failed = false;
let exitCode = EXIT_CODES.ERROR;
try {
modeValidation(args);

if (args.options.scanAllUnmanaged && args.options.file) {
throw new UnsupportedOptionCombinationError([
'file',
Expand Down
66 changes: 66 additions & 0 deletions src/cli/modes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as abbrev from 'abbrev';
import { UnsupportedOptionCombinationError, CustomError } from '../lib/errors';

interface ModeData {
allowedCommands: Array<string>;
config: (args) => [];
}

const modes: Record<string, ModeData> = {
container: {
allowedCommands: ['test', 'monitor'],
config: (args): [] => {
args['docker'] = true;

return args;
},
},
};

export function parseMode(mode: string, args): string {
if (isValidMode(mode)) {
const command: string = args._[0];

if (isValidCommand(mode, command)) {
configArgs(mode, args);
mode = args._.shift();
}
}

return mode;
}

export function modeValidation(args: object) {
const mode = args['command'];
const commands: Array<string> = args['options']._;

if (isValidMode(mode) && commands.length <= 1) {
const allowed = modes[mode].allowedCommands
.join(', ')
.replace(/, ([^,]*)$/, ' or $1');
const message = `use snyk ${mode} with ${allowed}`;

throw new CustomError(message);
}

const command = commands[0];
if (isValidMode(mode) && !isValidCommand(mode, command)) {
const notSupported = [mode, command];

throw new UnsupportedOptionCombinationError(notSupported);
}
}

function isValidMode(mode: string): boolean {
return Object.keys(modes).includes(mode);
}

function isValidCommand(mode: string, command: string): boolean {
const aliases = abbrev(modes[mode].allowedCommands);

return Object.keys(aliases).includes(command);
}

function configArgs(mode: string, args): [] {
return modes[mode].config(args);
}
36 changes: 36 additions & 0 deletions test/args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,39 @@ test('test command line test --container', (t) => {
t.notOk(result.options.container);
t.end();
});

test('test command line "container test"', (t) => {
const cliArgs = [
'/Users/dror/.nvm/versions/node/v6.9.2/bin/node',
'/Users/dror/work/snyk/snyk-internal/cli',
'container',
'test',
];
const result = args(cliArgs);
t.ok(result.options.docker);
t.end();
});

test('test command line "container monitor"', (t) => {
const cliArgs = [
'/Users/dror/.nvm/versions/node/v6.9.2/bin/node',
'/Users/dror/work/snyk/snyk-internal/cli',
'container',
'monitor',
];
const result = args(cliArgs);
t.ok(result.options.docker);
t.end();
});

test('test command line "container protect"', (t) => {
const cliArgs = [
'/Users/dror/.nvm/versions/node/v6.9.2/bin/node',
'/Users/dror/work/snyk/snyk-internal/cli',
'container',
'protect',
];
const result = args(cliArgs);
t.notOk(result.options.docker);
t.end();
});
212 changes: 212 additions & 0 deletions test/modes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
import { test } from 'tap';
import {
UnsupportedOptionCombinationError,
CustomError,
} from '../src/lib/errors';
import { parseMode, modeValidation } from '../src/cli/modes';

test('when is missing command', (c) => {
c.test('should do nothing', (t) => {
const cliCommand = 'container';
const cliArgs = {
_: [],
'package-manager': 'pip',
};

const command = parseMode(cliCommand, cliArgs);

t.equal(command, cliCommand);
t.equal(cliArgs, cliArgs);
t.notOk(cliArgs['docker']);
t.end();
});
c.end();
});

test('when is not a valid mode', (c) => {
c.test('should do nothing', (t) => {
const cliCommand = 'test';
const cliArgs = {
_: [],
'package-manager': 'pip',
};

const command = parseMode(cliCommand, cliArgs);

t.equal(command, cliCommand);
t.equal(cliArgs, cliArgs);
t.notOk(cliArgs['docker']);
t.end();
});
c.end();
});

test('when is a valid mode', (c) => {
c.test('when is allowed command', (d) => {
d.test(
'"container test" should set docker option and test command',
(t) => {
const expectedCommand = 'test';
const expectedArgs = {
_: [],
docker: true,
'package-manager': 'pip',
};
const cliCommand = 'container';
const cliArgs = {
_: ['test'],
'package-manager': 'pip',
};

const command = parseMode(cliCommand, cliArgs);

t.equal(command, expectedCommand);
t.same(cliArgs, expectedArgs);
t.ok(cliArgs['docker']);
t.end();
},
);

d.test('when there is a command alias', (t) => {
t.test('"container t" should set docker option and test command', (t) => {
const expectedCommand = 't';
const expectedArgs = {
_: [],
docker: true,
'package-manager': 'pip',
};
const cliCommand = 'container';
const cliArgs = {
_: ['t'],
'package-manager': 'pip',
};

const command = parseMode(cliCommand, cliArgs);

t.equal(command, expectedCommand);
t.same(cliArgs, expectedArgs);
t.ok(cliArgs['docker']);
t.end();
});
t.end();
});
d.end();
});

c.test('when is not allowed command', (d) => {
d.test(
'"container protect" should not set docker option and return same command',
(t) => {
const expectedCommand = 'container';
const expectedArgs = {
_: ['protect'],
'package-manager': 'pip',
};
const cliCommand = 'container';
const cliArgs = {
_: ['protect'],
'package-manager': 'pip',
};

const command = parseMode(cliCommand, cliArgs);

t.equal(command, expectedCommand);
t.same(cliArgs, expectedArgs);
t.notOk(cliArgs['docker']);
t.end();
},
);
d.end();
});

c.test('mode validation', (d) => {
d.test('when there is no command, throw error', (t) => {
const args = {
command: 'container',
options: {
_: ['container'],
},
};

try {
modeValidation(args);
} catch (err) {
t.ok(err instanceof CustomError, 'should throw CustomError');
t.equal(
err.message,
'use snyk container with test or monitor',
'should have error message',
);
t.end();
}
});

d.test('when command is not valid, throw error', (t) => {
const args = {
command: 'container',
options: {
_: ['protect', 'container'],
},
};

try {
modeValidation(args);
} catch (err) {
t.ok(
err instanceof UnsupportedOptionCombinationError,
'should throw UnsupportedOptionCombinationError',
);
t.equal(
err.message,
'The following option combination is not currently supported: container + protect',
'should have error message',
);
t.end();
}
});

d.test('when command is valid, do nothing', (t) => {
const args = {
command: 'container',
options: {
_: ['test', 'container'],
},
};

modeValidation(args);

t.ok('should not throw error');
t.end();
});

d.test('when there is no valid mode, do nothing', (t) => {
const args = {
command: 'test',
options: {
_: ['test'],
},
};

modeValidation(args);

t.ok('should not throw error');
t.end();
});

d.test('when there is no mode, do nothing', (t) => {
const args = {
command: '',
options: {
_: [],
},
};

modeValidation(args);

t.ok('should not throw error');
t.end();
});
d.end();
});
c.end();
});

0 comments on commit 6bc0085

Please sign in to comment.