Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add unmanaged service test call ff #3603

Merged
merged 1 commit into from
Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
212 changes: 211 additions & 1 deletion src/lib/ecosystems/resolve-test-facts.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,231 @@
import { Options, PolicyOptions } from '../types';
import { spinner } from '../../lib/spinner';
import { Ecosystem, ScanResult, TestResult } from './types';
import {
Ecosystem,
ScanResult,
TestResult,
FileSignaturesDetails,
} from './types';
import {
CreateDepGraphResponse,
GetIssuesResponse,
FileHashes,
Attributes,
} from './unmanaged/types';
import {
requestTestPollingToken,
pollingTestWithTokenUntilDone,
createDepGraph,
getDepGraph,
getIssues,
} from '../polling/polling-test';
import { extractAndApplyPluginAnalytics } from './plugin-analytics';
import { findAndLoadPolicy } from '../policy';
import { filterIgnoredIssues } from './policy';
import { IssueData, Issue } from '../snyk-test/legacy';
import { hasFeatureFlag } from '../feature-flags';
import { delayNextStep } from '../polling/common';
import {
convertDepGraph,
convertMapCasing,
convertToCamelCase,
getSelf,
} from './unmanaged/utils';

export async function resolveAndTestFacts(
ecosystem: Ecosystem,
scans: {
[dir: string]: ScanResult[];
},
options: Options & PolicyOptions,
): Promise<[TestResult[], string[]]> {
const unmanagedDepsOverride = process.env.USE_UNMANAGED_DEPS;

const featureFlagEnabled = await hasFeatureFlag(
'snykNewUnmanagedTest',
options,
);

return featureFlagEnabled || unmanagedDepsOverride
? resolveAndTestFactsUnmanagedDeps(scans, options)
: resolveAndTestFactsRegistry(ecosystem, scans, options);
}

async function submitHashes(
hashes: FileHashes,
orgId: string,
): Promise<string> {
const response: CreateDepGraphResponse = await createDepGraph(hashes, orgId);

return response.data.id;
}

async function pollDepGraph(id: string, orgId: string): Promise<Attributes> {
let attempts = 0;
const maxAttempts = 50;
while (attempts < maxAttempts) {
try {
const response = await getDepGraph(id, orgId);
return response.data.attributes;
} catch (e) {
await delayNextStep(attempts, maxAttempts, 1000);
attempts++;
}
}

return Promise.reject('Failed to get DepGraph');
}

async function fetchIssues(
start_time,
dep_graph_data,
component_details,
orgId: string,
) {
const response: GetIssuesResponse = await getIssues(
{
dep_graph: dep_graph_data,
start_time,
component_details,
},
orgId,
);

const issues = response.data.result.issues.map((issue) => {
const converted = convertToCamelCase<Issue>(issue);
converted.fixInfo = convertToCamelCase(converted.fixInfo);
return converted;
});

const issuesData = convertMapCasing<{
[issueId: string]: IssueData;
}>(response.data.result.issues_data);

const depGraphData = convertDepGraph(response.data.result.dep_graph);

const dependencyCount = response.data.result.dep_graph.graph.nodes.find(
(graphNode) => {
return graphNode.node_id === 'root-node';
},
)?.deps?.length;

const depsFilePaths = response.data.result.deps_file_paths;

const fileSignaturesDetails = convertMapCasing<FileSignaturesDetails>(
response.data.result.file_signatures_details,
);

return {
issues,
issuesData,
depGraphData,
dependencyCount,
depsFilePaths,
fileSignaturesDetails,
};
}

export async function resolveAndTestFactsUnmanagedDeps(
scans: {
[dir: string]: ScanResult[];
},
options: Options & PolicyOptions,
): Promise<[TestResult[], string[]]> {
const results: any[] = [];
const errors: string[] = [];
const packageManager = 'Unmanaged (C/C++)';

let orgId = options.org || '';

if (orgId === '') {
const self = await getSelf();
orgId = self.default_org_context;
}
danielroymoore marked this conversation as resolved.
Show resolved Hide resolved

for (const [path, scanResults] of Object.entries(scans)) {
await spinner(`Resolving and Testing fileSignatures in ${path}`);
for (const scanResult of scanResults) {
try {
const id = await submitHashes(
{ hashes: scanResult?.facts[0]?.data },
orgId,
);

const {
start_time,
dep_graph_data,
component_details,
} = await pollDepGraph(id, orgId);

const {
issues,
issuesData,
depGraphData,
dependencyCount,
depsFilePaths,
fileSignaturesDetails,
} = await fetchIssues(
start_time,
dep_graph_data,
component_details,
orgId,
);

const issuesMap: Map<string, Issue> = new Map();
issues.forEach((i) => {
issuesMap[i.issueId] = i;
});

const vulnerabilities: IssueData[] = [];
for (const issuesDataKey in issuesData) {
const issueData = issuesData[issuesDataKey];
const pkgCoordinate = `${issuesMap[issuesDataKey]?.pkgName}@${issuesMap[issuesDataKey]?.pkgVersion}`;
issueData.from = [pkgCoordinate];
issueData.name = pkgCoordinate;
issueData.packageManager = packageManager;
vulnerabilities.push(issueData);
}

const policy = await findAndLoadPolicy(path, 'cpp', options);

const [issuesFiltered, issuesDataFiltered] = filterIgnoredIssues(
issues,
issuesData,
policy,
);

results.push({
issues: issuesFiltered,
issuesData: issuesDataFiltered,
depGraphData,
depsFilePaths,
fileSignaturesDetails,
vulnerabilities,
path,
dependencyCount,
packageManager,
});
} catch (error) {
const hasStatusCodeError = error.code >= 400 && error.code <= 500;
if (hasStatusCodeError) {
errors.push(error.message);
continue;
}
const failedPath = path ? `in ${path}` : '.';
errors.push(`Could not test dependencies ${failedPath}`);
}
}
}
spinner.clearAll();
return [results, errors];
}

export async function resolveAndTestFactsRegistry(
ecosystem: Ecosystem,
scans: {
[dir: string]: ScanResult[];
},
options: Options & PolicyOptions,
): Promise<[TestResult[], string[]]> {
const results: any[] = [];
const errors: string[] = [];
Expand Down
4 changes: 2 additions & 2 deletions src/lib/ecosystems/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ interface UpgradePathItem {
isDropped?: boolean;
}

interface UpgradePath {
export interface UpgradePath {
path: UpgradePathItem[];
}

interface FixInfo {
export interface FixInfo {
upgradePaths: UpgradePath[];
isPatchable: boolean;
nearestFixedInVersion?: string;
Expand Down