Skip to content

Commit 84a1bb3

Browse files
author
Daniel Ekelund
committed
feat: use in_progress in unmanaged resp.
Use field in_progress in responses related to unmanaged projects to decide if we are going to perform a retry.
1 parent 8ff44cf commit 84a1bb3

File tree

5 files changed

+93
-41
lines changed

5 files changed

+93
-41
lines changed

src/lib/ecosystems/resolve-test-facts.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ import { findAndLoadPolicy } from '../policy';
2424
import { filterIgnoredIssues } from './policy';
2525
import { IssueData, Issue } from '../snyk-test/legacy';
2626
import { hasFeatureFlag } from '../feature-flags';
27-
import { delayNextStep } from '../polling/common';
2827
import {
2928
convertDepGraph,
3029
convertMapCasing,
3130
convertToCamelCase,
3231
getSelf,
3332
} from './unmanaged/utils';
33+
import { sleep } from '../common';
3434

3535
export async function resolveAndTestFacts(
3636
ecosystem: Ecosystem,
@@ -60,20 +60,31 @@ async function submitHashes(
6060
return response.data.id;
6161
}
6262

63-
async function pollDepGraph(id: string, orgId: string): Promise<Attributes> {
64-
let attempts = 0;
65-
const maxAttempts = 50;
66-
while (attempts < maxAttempts) {
67-
try {
68-
const response = await getDepGraph(id, orgId);
69-
return response.data.attributes;
70-
} catch (e) {
71-
await delayNextStep(attempts, maxAttempts, 1000);
72-
attempts++;
63+
async function pollDepGraphAttributes(
64+
id: string,
65+
orgId: string,
66+
): Promise<Attributes> {
67+
const maxIntervalMs = 60000;
68+
const minIntervalMs = 5000;
69+
70+
const maxAttempts = 31; // Corresponds to 25.5 minutes
71+
72+
// Loop until we receive a response that is not in progress,
73+
// or we receive something else than http status code 200.
74+
for (let i = 1; i <= maxAttempts; i++) {
75+
const graph = await getDepGraph(id, orgId);
76+
77+
if (graph.data.attributes.in_progress) {
78+
const pollInterval = Math.max(maxIntervalMs, minIntervalMs * i);
79+
await sleep(pollInterval * i);
80+
81+
continue;
7382
}
83+
84+
return graph.data.attributes;
7485
}
7586

76-
return Promise.reject('Failed to get DepGraph');
87+
throw new Error('max retries reached');
7788
}
7889

7990
async function fetchIssues(
@@ -144,6 +155,7 @@ export async function resolveAndTestFactsUnmanagedDeps(
144155

145156
for (const [path, scanResults] of Object.entries(scans)) {
146157
await spinner(`Resolving and Testing fileSignatures in ${path}`);
158+
147159
for (const scanResult of scanResults) {
148160
try {
149161
const id = await submitHashes(
@@ -155,7 +167,7 @@ export async function resolveAndTestFactsUnmanagedDeps(
155167
start_time,
156168
dep_graph_data,
157169
component_details,
158-
} = await pollDepGraph(id, orgId);
170+
} = await pollDepGraphAttributes(id, orgId);
159171

160172
const {
161173
issues,
@@ -178,11 +190,13 @@ export async function resolveAndTestFactsUnmanagedDeps(
178190

179191
const vulnerabilities: IssueData[] = [];
180192
for (const issuesDataKey in issuesData) {
181-
const issueData = issuesData[issuesDataKey];
182193
const pkgCoordinate = `${issuesMap[issuesDataKey]?.pkgName}@${issuesMap[issuesDataKey]?.pkgVersion}`;
194+
const issueData = issuesData[issuesDataKey];
195+
183196
issueData.from = [pkgCoordinate];
184197
issueData.name = pkgCoordinate;
185198
issueData.packageManager = packageManager;
199+
186200
vulnerabilities.push(issueData);
187201
}
188202

@@ -211,6 +225,7 @@ export async function resolveAndTestFactsUnmanagedDeps(
211225
errors.push(error.message);
212226
continue;
213227
}
228+
214229
const failedPath = path ? `in ${path}` : '.';
215230
errors.push(`Could not test dependencies ${failedPath}`);
216231
}

src/lib/ecosystems/unmanaged/types.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,9 @@ export interface DepGraphDataOpenAPI {
100100

101101
export interface Attributes {
102102
start_time: number;
103-
dep_graph_data: DepGraphDataOpenAPI;
104-
component_details: ComponentDetailsOpenApi;
103+
in_progress: boolean;
104+
dep_graph_data?: DepGraphDataOpenAPI;
105+
component_details?: ComponentDetailsOpenApi;
105106
}
106107

107108
export interface IssuesRequestDetails {
@@ -221,12 +222,6 @@ export interface GetIssuesResponse {
221222
data: IssuesResponseData;
222223
}
223224

224-
export interface GetDepGraphResponse {
225-
data: Data;
226-
jsonapi: JsonApi;
227-
links: Links;
228-
}
229-
230225
interface PatchOpenApi {
231226
version: string;
232227
id: string;

src/lib/polling/polling-test.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {
1717
import { ResolveAndTestFactsResponse } from './types';
1818
import { delayNextStep, handleProcessingStatus } from './common';
1919
import { TestDependenciesResult } from '../snyk-test/legacy';
20-
import { sleep } from '../common';
2120

2221
export async function getIssues(
2322
issuesRequestAttributes: IssuesRequestAttributes,
@@ -54,23 +53,7 @@ export async function getDepGraph(
5453
},
5554
};
5655

57-
const maxWaitingTimeMs = 30000;
58-
const pollIntervalMs = 5000;
59-
let waitingTimeMs = pollIntervalMs;
60-
let result = {} as GetDepGraphResponse;
61-
while (waitingTimeMs <= maxWaitingTimeMs) {
62-
try {
63-
await sleep(waitingTimeMs);
64-
result = await makeRequest<GetDepGraphResponse>(payload);
65-
break;
66-
} catch (e) {
67-
if (waitingTimeMs < maxWaitingTimeMs) {
68-
waitingTimeMs += pollIntervalMs;
69-
} else {
70-
throw e;
71-
}
72-
}
73-
}
56+
const result = await makeRequest<GetDepGraphResponse>(payload);
7457
return JSON.parse(result.toString());
7558
}
7659

@@ -89,6 +72,7 @@ export async function createDepGraph(
8972
},
9073
body: hashes,
9174
};
75+
9276
const result = await makeRequest<CreateDepGraphResponse>(payload);
9377
return JSON.parse(result.toString());
9478
}

test/jest/unit/lib/ecosystems/fixtures/get-dep-graph-response.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const componentDetailsOpenApi: ComponentDetailsOpenApi = {};
55

66
const attributes: Attributes = {
77
start_time: 1660137910316,
8+
in_progress: false,
89
dep_graph_data: depGraphDataOpenAPI,
910
component_details: componentDetailsOpenApi,
1011
};
@@ -14,3 +15,14 @@ export const getDepGraphResponse: Data = {
1415
type: 'depgraphs',
1516
attributes: attributes,
1617
}
18+
19+
const attributesInProgress: Attributes = {
20+
start_time: 1660137910316,
21+
in_progress: true,
22+
};
23+
24+
export const getDepGraphResponseInProgress: Data = {
25+
id: '1234',
26+
type: 'depgraphs',
27+
attributes: attributesInProgress,
28+
}

test/jest/unit/lib/ecosystems/resolve-test-facts.spec.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ import { DepsFilePaths } from 'snyk-cpp-plugin/dist/types';
2828
import { issuesResponseData } from './fixtures/issues-response';
2929
import { expectedTestResult } from './fixtures/expected-test-result-new-impl';
3030
import { createDepgraphResponse } from './fixtures/create-dep-graph-response';
31-
import { getDepGraphResponse } from './fixtures/get-dep-graph-response';
31+
import {
32+
getDepGraphResponse,
33+
getDepGraphResponseInProgress,
34+
} from './fixtures/get-dep-graph-response';
3235

3336
describe('resolve and test facts', () => {
3437
afterEach(() => jest.restoreAllMocks());
@@ -97,6 +100,7 @@ describe('resolve and test facts', () => {
97100
};
98101
const attributes: Attributes = {
99102
start_time: 0,
103+
in_progress: false,
100104
dep_graph_data: depGraphDataOpenAPI,
101105
component_details: componentDetailsOpenApi,
102106
};
@@ -296,6 +300,48 @@ describe('resolve and test facts', () => {
296300
expect(errors).toEqual([]);
297301
});
298302

303+
it('successfully resolving and testing file-signatures fact after a retry for c/c++ projects with new unmanaged service', async () => {
304+
const hasFeatureFlag: boolean | undefined = true;
305+
jest
306+
.spyOn(featureFlags, 'hasFeatureFlag')
307+
.mockResolvedValueOnce(hasFeatureFlag);
308+
309+
jest.spyOn(common, 'delayNextStep').mockImplementation();
310+
311+
jest.spyOn(pollingTest, 'createDepGraph').mockResolvedValueOnce({
312+
data: createDepgraphResponse,
313+
jsonapi: { version: 'v1.0' } as JsonApi,
314+
links: { self: '' } as Links,
315+
});
316+
317+
jest.spyOn(pollingTest, 'getDepGraph').mockResolvedValue({
318+
data: getDepGraphResponseInProgress,
319+
jsonapi: { version: 'v1.0' } as JsonApi,
320+
links: { self: '' } as Links,
321+
});
322+
323+
jest.spyOn(pollingTest, 'getDepGraph').mockResolvedValue({
324+
data: getDepGraphResponse,
325+
jsonapi: { version: 'v1.0' } as JsonApi,
326+
links: { self: '' } as Links,
327+
});
328+
329+
jest.spyOn(pollingTest, 'getIssues').mockResolvedValueOnce({
330+
data: issuesResponseData,
331+
jsonapi: { version: 'v1.0' } as JsonApi,
332+
links: { self: '' } as Links,
333+
});
334+
335+
const [testResults, errors] = await resolveAndTestFacts(
336+
'cpp',
337+
scanResults,
338+
{} as Options,
339+
);
340+
341+
expect(testResults).toEqual(expectedTestResult);
342+
expect(errors).toEqual([]);
343+
});
344+
299345
it('failed resolving and testing file-signatures since createDepGraph throws exception with new unmanaged service', async () => {
300346
const hasFeatureFlag: boolean | undefined = true;
301347
jest

0 commit comments

Comments
 (0)