Skip to content

Commit c5ee38e

Browse files
author
Amir Moualem
authored
Merge pull request #255 from snyk/feat/retry-transmissions
Feat/retry transmissions
2 parents ad2ccac + 59384fb commit c5ee38e

File tree

5 files changed

+61
-38
lines changed

5 files changed

+61
-38
lines changed

.eslintrc.json

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"prettier/@typescript-eslint"
1212
],
1313
"rules": {
14+
"@typescript-eslint/no-use-before-define": "off",
1415
"@typescript-eslint/interface-name-prefix": "off",
1516
"@typescript-eslint/await-thenable": "error",
1617
"@typescript-eslint/ban-ts-ignore": "error",

package-lock.json

+1-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"child-process-promise": "^2.2.1",
3737
"lru-cache": "^5.1.1",
3838
"needle": "^2.4.0",
39+
"sleep-promise": "^8.0.1",
3940
"snyk-config": "3.0.0",
4041
"snyk-docker-plugin": "1.34.0",
4142
"source-map-support": "^0.5.9",
@@ -48,7 +49,6 @@
4849
"@typescript-eslint/parser": "^2.6.1",
4950
"eslint": "^6.6.0",
5051
"eslint-config-prettier": "^6.5.0",
51-
"sleep-promise": "^8.0.1",
5252
"tap": "github:snyk/node-tap#alternative-runtimes",
5353
"ts-node": "^8.1.0",
5454
"tsc-watch": "^1.0.30"

src/transmitter/index.ts

+52-35
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,25 @@
1-
import needle = require('needle');
1+
import * as needle from 'needle';
2+
import { NeedleResponse, NeedleHttpVerbs } from 'needle';
3+
import * as sleep from 'sleep-promise';
24
import * as config from '../common/config';
35
import logger = require('../common/logger');
4-
import { IDeleteWorkloadPayload, IDepGraphPayload, IWorkloadMetadataPayload } from './types';
6+
import { IDeleteWorkloadPayload, IDepGraphPayload, IWorkloadMetadataPayload, IResponseWithAttempts } from './types';
57

68
const upstreamUrl = config.INTEGRATION_API || config.DEFAULT_KUBERNETES_UPSTREAM_URL;
79

8-
function isSuccessStatusCode(statusCode: number | undefined): boolean {
9-
return statusCode !== undefined && statusCode > 100 && statusCode < 400;
10-
}
11-
1210
export async function sendDepGraph(...payloads: IDepGraphPayload[]): Promise<void> {
1311
for (const payload of payloads) {
12+
// Intentionally removing dependencyGraph as it would be too big to log
13+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
14+
const { dependencyGraph, ...payloadWithoutDepGraph } = payload;
1415
try {
15-
const result = await needle('post', `${upstreamUrl}/api/v1/dependency-graph`, payload, {
16-
json: true,
17-
compressed: true,
18-
},
19-
);
20-
21-
if (!isSuccessStatusCode(result.statusCode)) {
22-
throw new Error(`${result.statusCode} ${result.statusMessage}`);
16+
const {response, attempt} = await retryRequest('post', `${upstreamUrl}/api/v1/dependency-graph`, payload);
17+
if (!isSuccessStatusCode(response.statusCode)) {
18+
throw new Error(`${response.statusCode} ${response.statusMessage}`);
19+
} else {
20+
logger.info({payload: payloadWithoutDepGraph, attempt}, 'dependency graph sent upstream successfully')
2321
}
2422
} catch (error) {
25-
// Intentionally removing dependencyGraph as it would be too big to log
26-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
27-
const { dependencyGraph, ...payloadWithoutDepGraph } = payload;
2823
logger.error({error, payload: payloadWithoutDepGraph}, 'could not send the dependency scan result to Homebase');
2924
}
3025
}
@@ -33,16 +28,12 @@ export async function sendDepGraph(...payloads: IDepGraphPayload[]): Promise<voi
3328
export async function sendWorkloadMetadata(payload: IWorkloadMetadataPayload): Promise<void> {
3429
try {
3530
logger.info({workloadLocator: payload.workloadLocator}, 'attempting to send workload metadata upstream')
36-
const result = await needle('post', `${upstreamUrl}/api/v1/workload`, payload, {
37-
json: true,
38-
compressed: true,
39-
},
40-
);
4131

42-
if (!isSuccessStatusCode(result.statusCode)) {
43-
throw new Error(`${result.statusCode} ${result.statusMessage}`);
32+
const {response, attempt} = await retryRequest('post', `${upstreamUrl}/api/v1/workload`, payload);
33+
if (!isSuccessStatusCode(response.statusCode)) {
34+
throw new Error(`${response.statusCode} ${response.statusMessage}`);
4435
} else {
45-
logger.info({workloadLocator: payload.workloadLocator}, 'workload metadata sent upstream successfully')
36+
logger.info({workloadLocator: payload.workloadLocator, attempt}, 'workload metadata sent upstream successfully')
4637
}
4738
} catch (error) {
4839
logger.error({error, workloadLocator: payload.workloadLocator}, 'could not send workload metadata to Homebase');
@@ -51,22 +42,48 @@ export async function sendWorkloadMetadata(payload: IWorkloadMetadataPayload): P
5142

5243
export async function deleteHomebaseWorkload(payload: IDeleteWorkloadPayload): Promise<void> {
5344
try {
54-
const result = await needle('delete', `${upstreamUrl}/api/v1/workload`, payload, {
55-
json: true,
56-
compressed: true,
57-
},
58-
);
59-
60-
if (result.statusCode === 404) {
45+
const {response, attempt} = await retryRequest('delete', `${upstreamUrl}/api/v1/workload`, payload);
46+
if (response.statusCode === 404) {
6147
const msg = 'attempted to delete a workload Homebase could not find, maybe we are still building it?';
6248
logger.info({payload}, msg);
6349
return;
6450
}
65-
66-
if (!isSuccessStatusCode(result.statusCode)) {
67-
throw new Error(`${result.statusCode} ${result.statusMessage}`);
51+
if (!isSuccessStatusCode(response.statusCode)) {
52+
throw new Error(`${response.statusCode} ${response.statusMessage}`);
53+
} else {
54+
logger.info({workloadLocator: payload.workloadLocator, attempt}, 'workload deleted successfully')
6855
}
6956
} catch (error) {
7057
logger.error({error, payload}, 'could not send workload to delete to Homebase');
7158
}
7259
}
60+
61+
function isSuccessStatusCode(statusCode: number | undefined): boolean {
62+
return statusCode !== undefined && statusCode > 100 && statusCode < 400;
63+
}
64+
65+
async function retryRequest(verb: NeedleHttpVerbs, url: string, payload: object): Promise<IResponseWithAttempts> {
66+
const retry = {
67+
attempts: 3,
68+
intervalSeconds: 2,
69+
}
70+
const options = {
71+
json: true,
72+
compressed: true,
73+
};
74+
75+
let response: NeedleResponse;
76+
let attempt = 1;
77+
78+
response = await needle(verb, url, payload, options);
79+
for (; attempt <= retry.attempts; attempt++) {
80+
if (response.statusCode === 502 && attempt + 1 < retry.attempts) {
81+
await sleep(retry.intervalSeconds * 1000);
82+
response = await needle(verb, url, payload, options);
83+
} else {
84+
break;
85+
}
86+
}
87+
88+
return {response, attempt};
89+
}

src/transmitter/types.ts

+6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { V1PodSpec } from '@kubernetes/client-node';
2+
import { NeedleResponse } from 'needle';
23

34
interface StringMap { [key: string]: string; }
45

@@ -66,3 +67,8 @@ export interface IWorkload {
6667
cluster: string;
6768
podSpec: V1PodSpec;
6869
}
70+
71+
export interface IResponseWithAttempts {
72+
response: NeedleResponse;
73+
attempt: number;
74+
}

0 commit comments

Comments
 (0)