Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/cli/commands/monitor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import {
} from '../../../lib/package-managers';
import { normalizeTargetFile } from '../../../lib/normalize-target-file';
import { getOrganizationID } from '../../../lib/organization';
import { shouldPrintEffectiveDepGraph } from '../../../lib/snyk-test/common';

const SEPARATOR = '\n-------------------------------------------------------\n';
const debug = Debug('snyk');
Expand Down Expand Up @@ -219,7 +220,8 @@ export default async function monitor(...args0: MethodArgs): Promise<any> {
const verboseEnabled =
args.includes('-Dverbose') ||
args.includes('-Dverbose=true') ||
!!options['print-graph'];
!!options['print-graph'] ||
shouldPrintEffectiveDepGraph(options);
if (verboseEnabled) {
enableMavenDverboseExhaustiveDeps = (await hasFeatureFlag(
MAVEN_DVERBOSE_EXHAUSTIVE_DEPS_FF,
Expand Down
17 changes: 15 additions & 2 deletions src/lib/ecosystems/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import { TestDependenciesResponse } from '../snyk-test/legacy';
import {
assembleQueryString,
printDepGraph,
printDepGraphJsonl,
shouldPrintDepGraph,
shouldPrintJsonlOutput,
} from '../snyk-test/common';
import { getAuthHeader } from '../api-token';
import { resolveAndTestFacts } from './resolve-test-facts';
Expand Down Expand Up @@ -56,7 +58,7 @@ export async function testEcosystem(

if (isUnmanagedEcosystem(ecosystem) && shouldPrintDepGraph(options)) {
const [target] = paths;
return printUnmanagedDepGraph(results, target, process.stdout);
return printUnmanagedDepGraph(results, target, process.stdout, options);
}

const [testResults, errors] = await selectAndExecuteTestStrategy(
Expand Down Expand Up @@ -99,11 +101,22 @@ export async function printUnmanagedDepGraph(
results: ScanResultsByPath,
target: string,
destination: Writable,
options: Options,
): Promise<TestCommandResult> {
const [result] = await getUnmanagedDepGraph(results);
const depGraph = convertDepGraph(result);

await printDepGraph(depGraph, target, destination);
if (shouldPrintJsonlOutput(options)) {
await printDepGraphJsonl(
depGraph,
target,
undefined,
undefined,
destination,
);
} else {
await printDepGraph(depGraph, target, destination);
}

return TestCommandResult.createJsonTestCommandResult('');
}
Expand Down
10 changes: 3 additions & 7 deletions src/lib/plugins/get-deps-from-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { convertSingleResultToMultiCustom } from './convert-single-splugin-res-t
import { convertMultiResultToMultiCustom } from './convert-multi-plugin-res-to-multi-custom';
import { processYarnWorkspaces } from './nodejs-plugin/yarn-workspaces-parser';
import { ScannedProject } from '@snyk/cli-interface/legacy/common';
import { shouldPrintDepGraphWithErrors } from '../snyk-test/common';

const debug = debugModule('snyk-test');

Expand Down Expand Up @@ -104,14 +105,9 @@ export async function getDepsFromPlugin(
}
let inspectRes;
try {
inspectRes = await getSinglePluginResult(
root,
options,
'',
featureFlags,
);
inspectRes = await getSinglePluginResult(root, options, '', featureFlags);
} catch (error) {
if (options['print-effective-graph-with-errors']) {
if (shouldPrintDepGraphWithErrors(options)) {
const errMessage =
error?.message ?? 'Something went wrong getting dependencies';
debug(
Expand Down
3 changes: 2 additions & 1 deletion src/lib/plugins/get-multi-plugin-result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { errorMessageWithRetry, FailedToRunTestError } from '../errors';
import { processYarnWorkspaces } from './nodejs-plugin/yarn-workspaces-parser';
import { processNpmWorkspaces } from './nodejs-plugin/npm-workspaces-parser';
import { processPnpmWorkspaces } from 'snyk-nodejs-plugin';
import { shouldPrintDepGraphWithErrors } from '../snyk-test/common';

const debug = debugModule('snyk-test');
export interface ScannedProjectCustom
Expand Down Expand Up @@ -184,7 +185,7 @@ export async function getMultiPluginResult(
if (!allResults.length) {
// When allow-incomplete-sbom is active, return instead of throwing
// so the caller can print per-project JSONL error entries
if (options['print-effective-graph-with-errors']) {
if (shouldPrintDepGraphWithErrors(options)) {
return {
plugin: {
name: 'custom-auto-detect',
Expand Down
61 changes: 33 additions & 28 deletions src/lib/snyk-test/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,67 +102,66 @@ export async function printDepGraph(
}

export function shouldPrintDepGraph(opts: Options): boolean {
return opts['print-graph'] && !opts['print-deps'];
return (
(!!opts['print-graph'] || !!opts['allow-incomplete-sbom']) &&
!shouldPrintEffectiveDepGraph(opts) &&
!opts['print-deps']
);
}

/**
* printEffectiveDepGraph writes the given, possibly pruned dep-graph and target file to the destination
* stream as a JSON object containing both depGraph, normalisedTargetFile and targetFile from plugin.
* This allows extracting the effective dep-graph which is being used for the test.
* printDepGraphJsonl writes dep-graph metadata to the destination stream as one JSON object
* per line (JSONL): depGraph, normalisedTargetFile, optional targetFileFromPlugin, optional target.
* Used for both complete (--allow-incomplete-sbom) and effective (--print-effective-graph*) graph paths.
* Callers supply the dep-graph payload (full or pruned) they want to serialize.
*/
export async function printEffectiveDepGraph(
export async function printDepGraphJsonl(
depGraph: DepGraphData,
normalisedTargetFile: string,
targetFileFromPlugin: string | undefined,
target: GitTarget | ContainerTarget | null | undefined,
destination: Writable,
): Promise<void> {
return new Promise((res, rej) => {
const effectiveGraphOutput = {
const record = {
depGraph,
normalisedTargetFile,
targetFileFromPlugin,
target,
};

new ConcatStream(
new JsonStreamStringify(effectiveGraphOutput),
Readable.from('\n'),
)
new ConcatStream(new JsonStreamStringify(record), Readable.from('\n'))
.on('end', res)
.on('error', rej)
.pipe(destination);
});
}

/**
* printEffectiveDepGraphError writes an error output for failed dependency graph resolution
* to the destination stream in a format consistent with printEffectiveDepGraph.
* This is used when --print-effective-graph-with-errors is set but dependency resolution failed.
* printDepGraphJsonlError writes a JSONL line for failed dependency graph resolution, shaped for
* consumers that read the same stream as printDepGraphJsonl.
* Used when --print-effective-graph-with-errors or --allow-incomplete-sbom is set.
*/
export async function printEffectiveDepGraphError(
export async function printDepGraphJsonlError(
root: string,
failedProjectScanError: FailedProjectScanError,
destination: Writable,
): Promise<void> {
return new Promise((res, rej) => {
// Normalize the target file path to be relative to root, consistent with printEffectiveDepGraph
// Normalize the target file path to be relative to root, consistent with printDepGraphJsonl
const normalisedTargetFile = failedProjectScanError.targetFile
? path.relative(root, failedProjectScanError.targetFile)
: failedProjectScanError.targetFile;

const problemError = getOrCreateErrorCatalogError(failedProjectScanError);
const serializedError = problemError.toJsonApi().body();

const effectiveGraphErrorOutput = {
const errorRecord = {
error: serializedError,
normalisedTargetFile,
};

new ConcatStream(
new JsonStreamStringify(effectiveGraphErrorOutput),
Readable.from('\n'),
)
new ConcatStream(new JsonStreamStringify(errorRecord), Readable.from('\n'))
.on('end', res)
.on('error', rej)
.pipe(destination);
Expand All @@ -171,20 +170,26 @@ export async function printEffectiveDepGraphError(

/**
* Checks if either --print-effective-graph or --print-effective-graph-with-errors is set.
* These flags request the pruned (effective) dependency graph in JSONL format.
*/
export function shouldPrintEffectiveDepGraph(opts: Options): boolean {
return (
!!opts['print-effective-graph'] ||
shouldPrintEffectiveDepGraphWithErrors(opts)
);
return !!(opts['print-effective-graph'] || opts['print-effective-graph-with-errors']);
}

/**
* shouldPrintJsonlOutput returns true when JSONL-format dependency graph output is
* requested — either via the effective-graph flags or via --allow-incomplete-sbom.
*/
export function shouldPrintJsonlOutput(opts: Options): boolean {
return shouldPrintEffectiveDepGraph(opts) || !!opts['allow-incomplete-sbom'];
}

/**
* shouldPrintEffectiveDepGraphWithErrors checks if the --print-effective-graph-with-errors flag is set.
* This is used to determine if the effective dep-graph with errors should be printed.
* shouldPrintDepGraphWithErrors returns true when error entries should also be included
* in the dependency graph output stream.
*/
export function shouldPrintEffectiveDepGraphWithErrors(opts: Options): boolean {
return !!opts['print-effective-graph-with-errors'];
export function shouldPrintDepGraphWithErrors(opts: Options): boolean {
return !!(opts['print-effective-graph-with-errors'] || opts['allow-incomplete-sbom']);
}

/**
Expand Down
4 changes: 3 additions & 1 deletion src/lib/snyk-test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const {
} = require('../package-managers');
const { getOrganizationID } = require('../organization');
const debug = require('debug')('snyk-test');
const { shouldPrintEffectiveDepGraph } = require('./common');

async function test(root, options, callback) {
if (typeof options === 'function') {
Expand Down Expand Up @@ -56,7 +57,8 @@ async function executeTest(root, options) {
const verboseEnabled =
args.includes('-Dverbose') ||
args.includes('-Dverbose=true') ||
!!options['print-graph'];
!!options['print-graph'] ||
shouldPrintEffectiveDepGraph(options);
if (verboseEnabled) {
enableMavenDverboseExhaustiveDeps = await hasFeatureFlag(
MAVEN_DVERBOSE_EXHAUSTIVE_DEPS_FF,
Expand Down
63 changes: 45 additions & 18 deletions src/lib/snyk-test/run-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ import {
RETRY_ATTEMPTS,
RETRY_DELAY,
printDepGraph,
printEffectiveDepGraph,
printEffectiveDepGraphError,
printDepGraphJsonl,
printDepGraphJsonlError,
assembleQueryString,
shouldPrintDepGraph,
shouldPrintEffectiveDepGraph,
shouldPrintEffectiveDepGraphWithErrors,
shouldPrintJsonlOutput,
shouldPrintDepGraphWithErrors,
} from './common';
import config from '../config';
import * as analytics from '../analytics';
Expand Down Expand Up @@ -246,7 +247,11 @@ async function sendAndParseResults(
): Promise<TestResult[]> {
const results: TestResult[] = [];
const ecosystem = getEcosystem(options);
const depGraphs = new Map<string, depGraphLib.DepGraphData>();
const depGraphPrintJobs: {
legacyTargetLabel: string;
graph: depGraphLib.DepGraphData;
normalisedTargetFile: string;
}[] = [];

await spinner.clear<void>(spinnerLbl)();
if (!options.quiet) {
Expand Down Expand Up @@ -322,7 +327,11 @@ async function sendAndParseResults(

if (ecosystem && depGraph) {
const targetName = scanResult ? constructProjectName(scanResult) : '';
depGraphs.set(targetName, depGraph.toJSON());
depGraphPrintJobs.push({
legacyTargetLabel: targetName,
graph: depGraph.toJSON(),
normalisedTargetFile: targetFile || displayTargetFile || '',
});
}

const legacyRes = convertIssuesToAffectedPkgs(response);
Expand Down Expand Up @@ -352,8 +361,18 @@ async function sendAndParseResults(

if (ecosystem && shouldPrintDepGraph(options)) {
await spinner.clear<void>(spinnerLbl)();
for (const [targetName, depGraph] of depGraphs.entries()) {
await printDepGraph(depGraph, targetName, process.stdout);
for (const job of depGraphPrintJobs) {
if (shouldPrintJsonlOutput(options)) {
await printDepGraphJsonl(
job.graph,
job.normalisedTargetFile || job.legacyTargetLabel,
undefined,
undefined,
process.stdout,
);
} else {
await printDepGraph(job.graph, job.legacyTargetLabel, process.stdout);
}
}
return [];
}
Expand Down Expand Up @@ -654,18 +673,14 @@ async function assembleLocalPayloads(
const failedResults = (deps as MultiProjectResultCustom).failedResults;
if (failedResults?.length) {
await spinner.clear<void>(spinnerLbl)();
// When printing effective dep-graph with errors, suppress warning output —
// the failures will be embedded in the generated SBOM as annotations.
const suppressWarnings = shouldPrintEffectiveDepGraphWithErrors(options);
const isNotJsonOrQueiet =
!options.json && !options.quiet && !suppressWarnings;
const isNotJsonOrQueiet = !options.json && !options.quiet;

const errorMessages = extractErrorMessages(
failedResults,
isNotJsonOrQueiet,
);

if (!options.json && !options.quiet && !suppressWarnings) {
if (isNotJsonOrQueiet) {
console.warn(
chalk.bold.red(
`${icon.ISSUE} ${failedResults.length}/${
Expand All @@ -679,9 +694,9 @@ async function assembleLocalPayloads(
failedResults,
);

if (shouldPrintEffectiveDepGraphWithErrors(options)) {
if (shouldPrintDepGraphWithErrors(options)) {
for (const failed of failedResults) {
await printEffectiveDepGraphError(root, failed, process.stdout);
await printDepGraphJsonlError(root, failed, process.stdout);
}
}

Expand Down Expand Up @@ -832,7 +847,17 @@ async function assembleLocalPayloads(
);
}

await printDepGraph(root.toJSON(), targetFile || '', process.stdout);
if (shouldPrintJsonlOutput(options)) {
await printDepGraphJsonl(
root.toJSON(),
targetFile || '',
project.plugin.targetFile,
target,
process.stdout,
);
} else {
await printDepGraph(root.toJSON(), targetFile || '', process.stdout);
}
}

const body: PayloadBody = {
Expand Down Expand Up @@ -871,15 +896,17 @@ async function assembleLocalPayloads(
});
}

const pruneIsRequired = options.pruneRepeatedSubdependencies;
const pruneIsRequired =
options.pruneRepeatedSubdependencies ||
shouldPrintEffectiveDepGraph(options);

if (packageManager) {
depGraph = await pruneGraph(depGraph, packageManager, pruneIsRequired);
}

if (shouldPrintEffectiveDepGraph(options)) {
spinner.clear<void>(spinnerLbl)();
await printEffectiveDepGraph(
await printDepGraphJsonl(
depGraph.toJSON(),
targetFile,
project.plugin.targetFile,
Expand Down
4 changes: 4 additions & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ export interface Options {
'print-deps'?: boolean;
'print-tree'?: boolean;
'print-dep-paths'?: boolean;
'print-graph'?: boolean;
'allow-incomplete-sbom'?: boolean;
'print-effective-graph'?: boolean;
'print-effective-graph-with-errors'?: boolean;
'remote-repo-url'?: string;
Expand Down Expand Up @@ -150,6 +152,8 @@ export interface MonitorOptions {
json?: boolean;
allSubProjects?: boolean;
'project-name'?: string;
'print-graph'?: boolean;
'allow-incomplete-sbom'?: boolean;
'print-deps'?: boolean;
'print-dep-paths'?: boolean;
'target-reference'?: string;
Expand Down
Loading