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,35 +37,48 @@ const getRenderEnvironmentSecrets = async (secretSync: TRenderSyncWithCredential
do { do {
const url = cursor ? `${baseUrl}?cursor=${cursor}` : baseUrl; const url = cursor ? `${baseUrl}?cursor=${cursor}` : baseUrl;
const { data } = await request.get<
{ try {
envVar: { const { data } = await request.get<
key: string; {
value: string; envVar: {
}; key: string;
cursor: string; value: string;
}[] };
>(url, { cursor: string;
headers: { }[]
Authorization: `Bearer ${apiKey}`, >(url, {
Accept: "application/json" headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json"
}
});
const secrets = data.map((item) => ({
key: item.envVar.key,
value: item.envVar.value
}));
allSecrets.push(...secrets);
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;
}
const secrets = data.map((item) => ({
key: item.envVar.key,
value: item.envVar.value
}));
allSecrets.push(...secrets);
cursor = data[data.length - 1]?.cursor;
} 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,22 +86,34 @@ const putEnvironmentSecret = async (secretSync: TRenderSyncWithCredentials, secr
} }
} = secretSync; } = secretSync;
await request.put( try {
`${IntegrationUrls.RENDER_API_URL}/v1/services/${destinationConfig.serviceId}/env-vars/${key}`, await request.put(
{ `${IntegrationUrls.RENDER_API_URL}/v1/services/${destinationConfig.serviceId}/env-vars/${key}`,
key, {
value: secretMap[key].value key,
}, value: secretMap[key].value
{ },
headers: { {
Authorization: `Bearer ${apiKey}`, headers: {
Accept: "application/json" Authorization: `Bearer ${apiKey}`,
Accept: "application/json"
}
} }
);
} 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 ?? "" }]));