diff --git a/backend/package-lock.json b/backend/package-lock.json index 6514ebb17..3741f7665 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -19,6 +19,7 @@ "await-to-js": "^3.0.0", "aws-sdk": "^2.1311.0", "axios": "^1.1.3", + "axios-retry": "^3.4.0", "bcrypt": "^5.1.0", "bigint-conversion": "^2.2.2", "builder-pattern": "^2.2.0", @@ -3473,6 +3474,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.18.10", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", @@ -5317,6 +5329,15 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/axios-retry": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.4.0.tgz", + "integrity": "sha512-VdgaP+gHH4iQYCCNUWF2pcqeciVOdGrBBAYUfTY+wPcO5Ltvp/37MLFNCmJKo7Gj3SHvCSdL8ouI1qLYJN3liA==", + "dependencies": { + "@babel/runtime": "^7.15.4", + "is-retry-allowed": "^2.2.0" + } + }, "node_modules/babel-jest": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", @@ -7604,6 +7625,17 @@ "node": ">=0.10.0" } }, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -12179,6 +12211,11 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -16584,6 +16621,14 @@ "@babel/helper-plugin-utils": "^7.19.0" } }, + "@babel/runtime": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "requires": { + "regenerator-runtime": "^0.13.11" + } + }, "@babel/template": { "version": "7.18.10", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", @@ -18075,6 +18120,15 @@ "proxy-from-env": "^1.1.0" } }, + "axios-retry": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.4.0.tgz", + "integrity": "sha512-VdgaP+gHH4iQYCCNUWF2pcqeciVOdGrBBAYUfTY+wPcO5Ltvp/37MLFNCmJKo7Gj3SHvCSdL8ouI1qLYJN3liA==", + "requires": { + "@babel/runtime": "^7.15.4", + "is-retry-allowed": "^2.2.0" + } + }, "babel-jest": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", @@ -19771,6 +19825,11 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" }, + "is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==" + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -23088,6 +23147,11 @@ "picomatch": "^2.2.1" } }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", diff --git a/backend/package.json b/backend/package.json index d3ce5a393..89bd52b07 100644 --- a/backend/package.json +++ b/backend/package.json @@ -10,6 +10,7 @@ "await-to-js": "^3.0.0", "aws-sdk": "^2.1311.0", "axios": "^1.1.3", + "axios-retry": "^3.4.0", "bcrypt": "^5.1.0", "bigint-conversion": "^2.2.2", "builder-pattern": "^2.2.0", diff --git a/backend/src/config/request.ts b/backend/src/config/request.ts new file mode 100644 index 000000000..afadd1c15 --- /dev/null +++ b/backend/src/config/request.ts @@ -0,0 +1,16 @@ +import axios from 'axios'; +import axiosRetry from 'axios-retry'; + +const axiosInstance = axios.create(); + +// add retry functionality to the axios instance +axiosRetry(axiosInstance, { + retries: 3, + retryDelay: (retryCount) => retryCount * 1000, // delay between retries (in milliseconds) + retryCondition: (error) => { + // only retry if the error is a network error or a 5xx server error + return axiosRetry.isNetworkError(error) || axiosRetry.isRetryableError(error); + }, +}); + +export default axiosInstance; \ No newline at end of file diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 718da1299..9809d9db0 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -30,6 +30,7 @@ import { INTEGRATION_FLYIO_API_URL, INTEGRATION_CIRCLECI_API_URL, } from "../variables"; +import axiosWithRetry from '../config/request'; /** * Sync/push [secrets] to [app] in integration named [integration] @@ -597,7 +598,7 @@ const syncSecretsVercel = async ({ // [secret.key]: secret // }), {}); - const vercelSecrets: VercelSecret[] = (await axios.get( + const vercelSecrets: VercelSecret[] = (await axiosWithRetry.get( `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`, { params, @@ -616,7 +617,7 @@ const syncSecretsVercel = async ({ for await (const vercelSecret of vercelSecrets) { if (vercelSecret.type === 'encrypted') { // case: secret is encrypted -> need to decrypt - const decryptedSecret = (await axios.get( + const decryptedSecret = (await axiosWithRetry.get( `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${vercelSecret.id}`, { params, @@ -679,7 +680,7 @@ const syncSecretsVercel = async ({ // Sync/push new secrets if (newSecrets.length > 0) { - await axios.post( + await axiosWithRetry.post( `${INTEGRATION_VERCEL_API_URL}/v10/projects/${integration.app}/env`, newSecrets, { @@ -695,7 +696,7 @@ const syncSecretsVercel = async ({ for await (const secret of updateSecrets) { if (secret.type !== 'sensitive') { const { id, ...updatedSecret } = secret; - await axios.patch( + await axiosWithRetry.patch( `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, updatedSecret, { @@ -710,7 +711,7 @@ const syncSecretsVercel = async ({ } for await (const secret of deleteSecrets) { - await axios.delete( + await axiosWithRetry.delete( `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, { params, @@ -721,7 +722,6 @@ const syncSecretsVercel = async ({ } ); } - } catch (err) { Sentry.setUser(null); Sentry.captureException(err);