Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
kgilpin committed Aug 3, 2022
1 parent cb7bcc1 commit e810dbf
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/scanner/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ScanCommand from './cli/scan/command';
import UploadCommand from './cli/upload/command';
import CICommand from './cli/ci/command';
import MergeCommand from './cli/merge/command';
import TriageCommand from './cli/triage/command';
import { verbose } from './rules/lib/util';
import { AbortError, ValidationError } from './errors';
import { ExitCode } from './cli/exitCode';
Expand Down Expand Up @@ -38,6 +39,7 @@ yargs(process.argv.slice(2))
alias: 'v',
})
.command(ScanCommand)
.command(TriageCommand)
.command(UploadCommand)
.command(CICommand)
.command(MergeCommand)
Expand Down
117 changes: 117 additions & 0 deletions packages/scanner/src/cli/triage/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Arguments, Argv } from 'yargs';
import readline from 'readline';

import { verbose } from '../../rules/lib/util';

import CommandOptions from './options';
import { parseConfigFile } from '../../configuration/configurationProvider';
import { handleWorkingDirectory } from '../handleWorkingDirectory';
import { FindingState, TriageItem } from '../../configuration/types/configuration';
import assert from 'assert';
import { writeFile } from 'fs/promises';
import { dump } from 'js-yaml';

export default {
command: 'triage <finding-hash+>',
describe: 'Triage findings by assigning them to a workflow state',
builder(args: Argv): Argv {
args.option('comment', {
describe: 'Comment to associate with the triage action',
alias: ['c'],
});

args.option('state', {
describe: 'Workflow state to assign to the finding',
type: 'string',
demandOption: true,
options: ['as-designed', 'deferred'],
});

args.positional('finding-hash', {
describe: 'Identifying hash (digest) of the finding',
type: 'string',
array: true,
});

return args.strict();
},
async handler(options: Arguments): Promise<void> {
let { comment } = options as unknown as CommandOptions;
const {
config: configFileName,
directory,
verbose: isVerbose,
state: stateStr,
findingHash: findingHashes,
} = options as unknown as CommandOptions;

if (isVerbose) {
verbose(true);
}

handleWorkingDirectory(directory);
const state = stateStr as FindingState;
const config = await parseConfigFile(configFileName);

if (process.stdout.isTTY) {
const ui = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

if (!comment) ui.question('Comment (optional): ', (answer) => (comment = answer));
}

const findings: Record<FindingState, TriageItem[]> =
config.findings || ({} as Record<FindingState, TriageItem[]>);
if (!config.findings) {
config.findings = findings;
}
assert(findings, 'findings is undefined');

if (!findings[state]) findings[state] = [];

const existingFindingHashes = new Set();
findingHashes.forEach((hash) => {
if (existingFindingHashes.has(hash)) return;

Object.entries(findings).forEach((entry) => {
const existingState = entry[0];
const findingsInState = entry[1];
const existingItem = findingsInState.find((item) => item.hash === hash);
if (existingItem) {
if (existingItem.comment !== comment) {
existingItem.comment = comment;
existingItem.updated_at = new Date(Date.now());
}
if (existingState !== state) {
findingsInState.splice(findingsInState.indexOf(existingItem), 1);
existingItem.updated_at = new Date(Date.now());
findings[state].push(existingItem);
}
}
});
});

findingHashes.forEach((hash) => {
if (existingFindingHashes.has(hash)) return;

const findingItem = {
hash,
updated_at: new Date(Date.now()),
comment,
} as TriageItem;
findings[state].push(findingItem);
});

Object.keys(findings).forEach((state) => {
findings[state as FindingState] = findings[state as FindingState].sort((a, b) => {
let diff = b.updated_at.getTime() - a.updated_at.getTime();
if (diff === 0) diff = a.hash.localeCompare(b.hash);
return diff;
});
});

await writeFile(configFileName, dump(config));
},
};
8 changes: 8 additions & 0 deletions packages/scanner/src/cli/triage/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default interface CommandOptions {
directory?: string;
config: string;
verbose?: boolean;
findingHash: string[];
state: string;
comment?: string;
}
9 changes: 9 additions & 0 deletions packages/scanner/src/configuration/types/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import CheckConfig from './checkConfig';

export type FindingState = 'as-designed' | 'deferred';

export interface TriageItem {
hash: string;
updated_at: Date;
comment?: string;
}

/**
* Configuration is the code representation of the scanner configuration file.
*/
export default interface Configuration {
checks: CheckConfig[];
findings?: Record<FindingState, TriageItem[]>;
}

0 comments on commit e810dbf

Please sign in to comment.