Skip to content

Commit

Permalink
Merge pull request #3101 from snyk/chore/CFG-1577
Browse files Browse the repository at this point in the history
feat: restructure the issues list layout
  • Loading branch information
YairZ101 committed Apr 13, 2022
2 parents 5fc7674 + 42a4d83 commit 697c1e3
Show file tree
Hide file tree
Showing 13 changed files with 1,099 additions and 181 deletions.
34 changes: 21 additions & 13 deletions src/cli/commands/test/iac/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ import {
import { isIacShareResultsOptions } from './local-execution/assert-iac-options-flag';
import { assertIaCOptionsFlags } from './local-execution/assert-iac-options-flag';
import { hasFeatureFlag } from '../../../../lib/feature-flags';
import { formatIacTestSummary } from '../../../../lib/formatters/iac-output';
import {
formatIacTestSummary,
getIacDisplayedIssues,
} from '../../../../lib/formatters/iac-output';

const debug = Debug('snyk-test');
const SEPARATOR = '\n-------------------------------------------------------\n';
Expand Down Expand Up @@ -241,18 +244,23 @@ export default async function(...args: MethodArgs): Promise<TestCommandResult> {
throw err;
}

let response = results
.map((result, i) => {
return displayResult(
results[i] as LegacyVulnApiResult,
{
...resultOptions[i],
isNewIacOutputSupported,
},
result.foundProjectCount,
);
})
.join(`\n${SEPARATOR}`);
let response = '';

if (isNewIacOutputSupported && !notSuccess) {
response += getIacDisplayedIssues(results, iacOutputMeta!);
} else {
response += results
.map((result, i) => {
return displayResult(
results[i] as LegacyVulnApiResult,
{
...resultOptions[i],
},
result.foundProjectCount,
);
})
.join(`\n${SEPARATOR}`);
}

if (notSuccess) {
debug(`Failed to test ${errorResults.length} projects, errors:`);
Expand Down
18 changes: 3 additions & 15 deletions src/lib/formatters/iac-output/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,7 @@
import * as v1 from './v1';
import * as v2 from './v2';
import { IacTestResponse } from '../../snyk-test/iac-test-result';
import { IacFileInDirectory } from '../../types';

export { formatIacTestSummary } from './v2';

export function getIacDisplayedOutput(
iacTest: IacTestResponse,
testedInfoText: string,
meta: string,
prefix: string,
isNewIacOutputSupported?: boolean,
): string {
return isNewIacOutputSupported
? v2.getIacDisplayedOutput(iacTest, prefix)
: v1.getIacDisplayedOutput(iacTest, testedInfoText, meta, prefix);
}

export function getIacDisplayErrorFileOutput(
iacFileResult: IacFileInDirectory,
isNewIacOutputSupported?: boolean,
Expand All @@ -30,4 +15,7 @@ export {
capitalizePackageManager,
createSarifOutputForIac,
shareResultsOutput,
getIacDisplayedOutput,
} from './v1';

export { formatIacTestSummary, getIacDisplayedIssues } from './v2';
28 changes: 28 additions & 0 deletions src/lib/formatters/iac-output/v2/formatters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { FormattedResult } from '../../../../cli/commands/test/iac/local-execution/types';
import { IacOutputMeta } from '../../../types';
import { IacTestOutput } from './types';

export function formatScanResultsNewOutput(
oldFormattedResults: FormattedResult[],
outputMeta: IacOutputMeta,
): IacTestOutput {
const newFormattedResults: IacTestOutput = {
results: {},
metadata: outputMeta,
};

oldFormattedResults.forEach((oldFormattedResult) => {
oldFormattedResult.result.cloudConfigResults.forEach((policyMetadata) => {
if (!newFormattedResults.results[policyMetadata.severity]) {
newFormattedResults.results[policyMetadata.severity] = [];
}
newFormattedResults.results[policyMetadata.severity].push({
policyMetadata,
targetFile: oldFormattedResult.targetFile,
targetFilePath: oldFormattedResult.targetFilePath,
});
});
});

return newFormattedResults;
}
75 changes: 1 addition & 74 deletions src/lib/formatters/iac-output/v2/index.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,10 @@
import chalk from 'chalk';
import { icon } from '../../../theme';
import * as Debug from 'debug';
import * as pathLib from 'path';

import {
IacTestResponse,
AnnotatedIacIssue,
} from '../../../../lib/snyk-test/iac-test-result';
import { printPath } from '../../remediation-based-format-issues';
import { titleCaseText } from '../../legacy-format-issue';
import { colorTextBySeverity } from '../../../../lib/snyk-test/common';
import { IacFileInDirectory } from '../../../../lib/types';
import { getSeverityValue } from '../../get-severity-value';

export { getIacDisplayedIssues } from './issues-list';
export { formatIacTestSummary } from './test-summary';

const debug = Debug('iac-output');

function formatIacIssue(
issue: AnnotatedIacIssue,
isNew: boolean,
path: string[],
): string {
const newBadge = isNew ? ' (new)' : '';
const name = issue.subType ? ` in ${chalk.bold(issue.subType)}` : '';

let introducedBy = '';
if (path) {
// In this mode, we show only one path by default, for compactness
const pathStr = printPath(path, 0);
introducedBy = `\n introduced by ${pathStr}`;
}

return (
colorTextBySeverity(
issue.severity,
` ${icon.ISSUE} ${chalk.bold(issue.title)}${newBadge} [${titleCaseText(
issue.severity,
)} Severity]`,
) +
` [${issue.id}]` +
name +
introducedBy +
'\n'
);
}

export function getIacDisplayedOutput(
iacTest: IacTestResponse,
prefix: string,
): string {
const issuesTextArray = [
chalk.bold.white('\nInfrastructure as code issues:'),
];

const NotNew = false;

const issues: AnnotatedIacIssue[] = iacTest.result.cloudConfigResults;
debug(`iac display output - ${issues.length} issues`);

issues
.sort((a, b) => getSeverityValue(b.severity) - getSeverityValue(a.severity))
.forEach((issue) => {
issuesTextArray.push(
formatIacIssue(issue, NotNew, issue.cloudConfigPath),
);
});

const issuesInfoOutput: string[] = [];
debug(`Iac display output - ${issuesTextArray.length} issues text`);
if (issuesTextArray.length > 0) {
issuesInfoOutput.push(issuesTextArray.join('\n'));
}

const body = issuesInfoOutput.join('\n\n');

return prefix + body;
}

export function getIacDisplayErrorFileOutput(
iacFileResult: IacFileInDirectory,
): string {
Expand Down
51 changes: 51 additions & 0 deletions src/lib/formatters/iac-output/v2/issues-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import chalk from 'chalk';
import { EOL } from 'os';
import capitalize = require('lodash/capitalize');
import debug = require('debug');

import { FormattedResult } from '../../../../cli/commands/test/iac/local-execution/types';
import { IacOutputMeta } from '../../../types';
import { severityColor } from './color-utils';
import { formatScanResultsNewOutput } from './formatters';
import { FormattedIssue } from './types';

export function getIacDisplayedIssues(
results: FormattedResult[],
outputMeta: IacOutputMeta,
): string {
const formattedResults = formatScanResultsNewOutput(results, outputMeta);

let output = EOL + chalk.bold.white('Issues') + EOL;

['low', 'medium', 'high', 'critical'].forEach((severity) => {
if (formattedResults.results[severity]) {
const issues = formattedResults.results[severity];
output +=
EOL +
severityColor[severity](
chalk.bold(
`${capitalize(severity)} Severity Issues: ${issues.length}`,
),
) +
EOL.repeat(2);
output += getIssuesOutput(issues);

debug(
`iac display output - ${severity} severity ${issues.length} issues`,
);
}
});

return output;
}

// CFG-1574 will continue the work on this function
function getIssuesOutput(issues: FormattedIssue[]) {
let output = '';

issues.forEach((issue) => {
output += chalk.white(`${issue.policyMetadata.title}`) + EOL;
});

return output;
}
19 changes: 19 additions & 0 deletions src/lib/formatters/iac-output/v2/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
import { IacTestResponse } from '../../../snyk-test/iac-test-result';
import { PolicyMetadata } from '../../../../cli/commands/test/iac/local-execution/types';
import { SEVERITY } from '../../../snyk-test/common';
import { IacOutputMeta } from '../../../types';

export interface IacTestData {
ignoreCount: number;
results: IacTestResponse[];
}

export type FormattedIssue = {
policyMetadata: PolicyMetadata;
// Decide which one of them to keep
targetFile: string;
targetFilePath: string;
};

type FormattedResultsBySeverity = {
[severity in keyof SEVERITY]?: FormattedIssue[];
};

export type IacTestOutput = {
results: FormattedResultsBySeverity;
metadata: IacOutputMeta;
};
7 changes: 1 addition & 6 deletions src/lib/formatters/test/display-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,9 @@ import {
} from '../../../lib/formatters/test/format-test-results';
import { showMultiScanTip } from '../show-multi-scan-tip';

interface DisplayResultOptions extends Options, TestOptions {
isNewIacOutputSupported?: boolean;
}

export function displayResult(
res: TestResult,
options: DisplayResultOptions,
options: Options & TestOptions,
foundProjectCount?: number,
) {
const meta = formatTestMeta(res, options);
Expand Down Expand Up @@ -120,7 +116,6 @@ export function displayResult(
testedInfoText,
meta,
prefix,
options.isNewIacOutputSupported,
);
}

Expand Down
7 changes: 7 additions & 0 deletions test/acceptance/fake-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,13 @@ export const fakeServer = (basePath: string, snykToken: string): FakeServer => {
return;
}

if (org === 'tf-lang-support' && flag === 'iacTerraformVarSupport') {
res.send({
ok: true,
});
return;
}

if (featureFlags.has(flag)) {
const ffEnabled = featureFlags.get(flag);
if (ffEnabled) {
Expand Down
16 changes: 16 additions & 0 deletions test/jest/acceptance/iac/iac-output.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ describe('iac test output', () => {
);
});

it('should show a subtitle for medium severity issues', async () => {
const { stdout } = await run('snyk iac test ./iac/arm/rule_test.json');

expect(stdout).toContain(
'Issues' + EOL.repeat(2) + 'Medium Severity Issues: 1',
);
});

it('should not show an initial message for JSON output', async () => {
const { stdout } = await run(
'snyk iac test --json ./iac/arm/rule_test.json',
Expand Down Expand Up @@ -176,6 +184,14 @@ If the issue persists contact support@snyk.io`,
expect(stdout).not.toContain(initialMessage);
});

it('should not show a subtitle for medium severity issues', async () => {
const { stdout } = await run('snyk iac test ./iac/arm/rule_test.json');

expect(stdout).not.toContain(
'Issues' + EOL.repeat(2) + 'Medium Severity Issues: 1',
);
});

it('should not display the test summary section', async () => {
// Arrange
const filePath = 'iac/kubernetes/pod-valid.json';
Expand Down
Loading

0 comments on commit 697c1e3

Please sign in to comment.