Improve render retries and rate limits

This commit is contained in:
Carlos Monastyrski
2025-07-24 17:49:28 -03:00
parent f70a1e3db6
commit eadb1a63fa
@@ -8,7 +8,22 @@ import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { TRenderSecret, TRenderSyncWithCredentials } from "./render-sync-types"; import { TRenderSecret, TRenderSyncWithCredentials } from "./render-sync-types";
const getRenderEnvironmentSecrets = async (secretSync: TRenderSyncWithCredentials) => { const MAX_RETRIES = 5;
const sleep = async () =>
new Promise((resolve) => {
setTimeout(resolve, 2000);
});
const retrySleep = async () =>
new Promise((resolve) => {
setTimeout(resolve, 60000);
});
const getRenderEnvironmentSecrets = async (
secretSync: TRenderSyncWithCredentials,
attempt = 0
): Promise<TRenderSecret[]> => {
const { const {
destinationConfig, destinationConfig,
connection: { connection: {
@@ -22,6 +37,8 @@ const getRenderEnvironmentSecrets = async (secretSync: TRenderSyncWithCredential
do { do {
const url = cursor ? `${baseUrl}?cursor=${cursor}` : baseUrl; const url = cursor ? `${baseUrl}?cursor=${cursor}` : baseUrl;
try {
const { data } = await request.get< const { data } = await request.get<
{ {
envVar: { envVar: {
@@ -43,14 +60,25 @@ const getRenderEnvironmentSecrets = async (secretSync: TRenderSyncWithCredential
})); }));
allSecrets.push(...secrets); allSecrets.push(...secrets);
cursor = data[data.length - 1]?.cursor; cursor = data[data.length - 1]?.cursor;
} catch (error) {
if (isAxiosError(error) && error.response?.status === 429 && attempt < MAX_RETRIES) {
await retrySleep();
return await getRenderEnvironmentSecrets(secretSync, attempt + 1);
}
throw error;
}
} while (cursor); } while (cursor);
return allSecrets; return allSecrets;
}; };
const putEnvironmentSecret = async (secretSync: TRenderSyncWithCredentials, secretMap: TSecretMap, key: string) => { const putEnvironmentSecret = async (
secretSync: TRenderSyncWithCredentials,
secretMap: TSecretMap,
key: string,
attempt = 0
): Promise<void> => {
const { const {
destinationConfig, destinationConfig,
connection: { connection: {
@@ -58,6 +86,7 @@ const putEnvironmentSecret = async (secretSync: TRenderSyncWithCredentials, secr
} }
} = secretSync; } = secretSync;
try {
await request.put( await request.put(
`${IntegrationUrls.RENDER_API_URL}/v1/services/${destinationConfig.serviceId}/env-vars/${key}`, `${IntegrationUrls.RENDER_API_URL}/v1/services/${destinationConfig.serviceId}/env-vars/${key}`,
{ {
@@ -71,9 +100,20 @@ const putEnvironmentSecret = async (secretSync: TRenderSyncWithCredentials, secr
} }
} }
); );
} catch (error) {
if (isAxiosError(error) && error.response?.status === 429 && attempt < MAX_RETRIES) {
await retrySleep();
return await putEnvironmentSecret(secretSync, secretMap, key, attempt + 1);
}
throw error;
}
}; };
const deleteEnvironmentSecret = async (secretSync: TRenderSyncWithCredentials, secret: Pick<TRenderSecret, "key">) => { const deleteEnvironmentSecret = async (
secretSync: TRenderSyncWithCredentials,
secret: Pick<TRenderSecret, "key">,
attempt = 0
): Promise<void> => {
const { const {
destinationConfig, destinationConfig,
connection: { connection: {
@@ -97,18 +137,19 @@ const deleteEnvironmentSecret = async (secretSync: TRenderSyncWithCredentials, s
return; return;
} }
if (isAxiosError(error) && error.response?.status === 429 && attempt < MAX_RETRIES) {
await retrySleep();
return await deleteEnvironmentSecret(secretSync, secret, attempt + 1);
}
throw error; throw error;
} }
}; };
const sleep = async () =>
new Promise((resolve) => {
setTimeout(resolve, 500);
});
export const RenderSyncFns = { export const RenderSyncFns = {
syncSecrets: async (secretSync: TRenderSyncWithCredentials, secretMap: TSecretMap) => { syncSecrets: async (secretSync: TRenderSyncWithCredentials, secretMap: TSecretMap) => {
const renderSecrets = await getRenderEnvironmentSecrets(secretSync); const renderSecrets = await getRenderEnvironmentSecrets(secretSync);
for await (const key of Object.keys(secretMap)) { for await (const key of Object.keys(secretMap)) {
// If value is empty skip it as render does not allow empty variables // If value is empty skip it as render does not allow empty variables
if (secretMap[key].value === "") { if (secretMap[key].value === "") {
@@ -132,6 +173,7 @@ export const RenderSyncFns = {
} }
} }
}, },
getSecrets: async (secretSync: TRenderSyncWithCredentials): Promise<TSecretMap> => { getSecrets: async (secretSync: TRenderSyncWithCredentials): Promise<TSecretMap> => {
const renderSecrets = await getRenderEnvironmentSecrets(secretSync); const renderSecrets = await getRenderEnvironmentSecrets(secretSync);
return Object.fromEntries(renderSecrets.map((secret) => [secret.key, { value: secret.value ?? "" }])); return Object.fromEntries(renderSecrets.map((secret) => [secret.key, { value: secret.value ?? "" }]));