UNPKG

clever-tools

Version:

Command Line Interface for Clever Cloud.

94 lines (84 loc) • 3.42 kB
import { getAllDeployments, getDeployment } from '@clevercloud/client/esm/api/v2/application.js'; import { setTimeout as delay } from 'node:timers/promises'; import { Logger } from '../logger.js'; import { sendToApi } from './send-to-api.js'; const DEPLOYMENT_POLLING_DELAY = 5000; const BACKOFF_FACTOR = 1.25; const INIT_RETRY_TIMEOUT = 1500; const MAX_RETRY_COUNT = 5; export async function waitForDeploymentStart({ ownerId, appId, deploymentId, commitId, knownDeployments }) { return waitFor(async () => { try { // In a deploy situation, we don't have the deployment ID so we get the latest deployments, // then we match by commit ID and we filter out "known deployments" that existed before the deploy. // In a restart situation, we have a deployment ID but fetching it too soon may result in an error so we get latest deployments, // then we just match on the deployment ID. const deploymentList = await getAllDeployments({ id: ownerId, appId, limit: 5 }).then(sendToApi); const deployment = deploymentList.find((d) => { if (deploymentId != null) { return d.uuid === deploymentId; } if (commitId != null && Array.isArray(knownDeployments)) { const isNew = knownDeployments.every(({ uuid }) => uuid !== d.uuid); return isNew && d.commit === commitId; } return false; }); if (deployment != null) { Logger.debug(`Deployment has started (state:${deployment.state})`); return deployment; } Logger.debug('Deployment cannot be found yet'); } catch (e) { Logger.debug('Failed to retrieve deployment'); throw e; } }); } export async function waitForDeploymentEnd({ ownerId, appId, deploymentId }) { return waitFor(async () => { try { const deployment = await getDeployment({ id: ownerId, appId, deploymentId }).then(sendToApi); // If it's not WIP, it means it has ended (OK, FAIL, CANCELLED…) if (deployment.state !== 'WIP') { Logger.debug(`Deployment is finished (state:${deployment.state})`); return deployment; } Logger.debug(`Deployment is not finished yet (state:${deployment.state})`); } catch (e) { Logger.debug('Failed to retrieve current deployment status'); throw e; } }); } // Calls an async function "fetchResult" // Return fetchResult's result if it's not null // Retry with simple "infinite polling" if fetchResult succeeds and returns null // Retry with exponential backoff if fetchResult fails async function waitFor(fetchResult) { let failCount = 0; while (true) { try { const result = await fetchResult(); if (result != null) { return result; } // Reset fail count, we only use it to limit failed API calls failCount = 0; // Retry with simple polling when API calls succeed await delay(DEPLOYMENT_POLLING_DELAY); } catch (e) { // If only retry if it's a network error if (e.code !== 'EAI_AGAIN') { throw e; } // Increment fail count so we don't retry more than MAX_RETRY_COUNT failCount += 1; if (failCount > MAX_RETRY_COUNT) { throw new Error(`Failed ${MAX_RETRY_COUNT} times!`); } // If API call fails, retry with an exponential backoff await delay(INIT_RETRY_TIMEOUT * BACKOFF_FACTOR ** failCount); } } }