All files / src/models deployments.js

52.68% Statements 49/93
55.55% Branches 5/9
66.66% Functions 2/3
52.68% Lines 49/93

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 193x 2x 2x 2x 2x 2x 2x 2x 2x 2x     2x 2x 2x 2x   2x 2x 2x 2x 2x           2x 2x 193x 193x                               193x 193x 193x 193x 193x 2x 2x 2x 2x 2x 2x 2x 2x 2x                                           2x 2x  
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);
    }
  }
}