All files / management healthCheck.ts

100% Statements 23/23
66.66% Branches 2/3
100% Functions 3/3
100% Lines 23/23

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      1x   1x 1x 1x                               1x                         3x 3x   2x   1x   1x                 1x         3x   3x       3x 25x 20x     25x 25x   25x   25x 2x   2x     23x      
import { Logger } from '@azure/functions'
import { StatusInfo } from '../shared/status'
import { StringDictionary, WebSiteManagementClient } from '@azure/arm-appservice'
import { performRollback } from './rollback'
import { ContainerClient } from '@azure/storage-blob'
import { removeOldFunctionFromStorage } from './storage'
import { eq } from 'semver'
import { ConstantBackoff, handleAll, retry } from 'cockatiel'
 
export interface PerformHealthCheckAfterUpdateParams {
  newVersion: string
  newFunctionZipUrl: string
  oldFunctionZipUrl: string
  logger?: Logger
  statusUrl: string
  settings: StringDictionary
  client: WebSiteManagementClient
  resourceGroupName: string
  appName: string
  storageClient: ContainerClient
  checkInterval?: number
}
 
export async function performHealthCheckAfterUpdate({
  newVersion,
  statusUrl,
  logger,
  settings,
  appName,
  client,
  resourceGroupName,
  oldFunctionZipUrl,
  storageClient,
  checkInterval,
  newFunctionZipUrl,
}: PerformHealthCheckAfterUpdateParams) {
  try {
    await runHealthCheckSchedule(statusUrl, newVersion, checkInterval, logger)
 
    await removeOldFunctionFromStorage(oldFunctionZipUrl, newFunctionZipUrl, storageClient, logger)
  } catch (error) {
    logger?.error('Health check failed', error)
 
    await performRollback({
      oldFunctionZipUrl,
      settings,
      appName,
      client,
      resourceGroupName,
      logger,
    })
 
    throw error
  }
}
 
async function runHealthCheckSchedule(url: string, newVersion: string, checkInterval = 10_000, logger?: Logger) {
  logger?.verbose(`Starting health check at ${url}`)
 
  const policy = retry(handleAll, {
    maxAttempts: 20,
    backoff: new ConstantBackoff(checkInterval),
  })
  return policy.execute(async ({ attempt, signal }) => {
    if (attempt > 1) {
      logger?.verbose(`Attempt ${attempt} at health check...`)
    }
 
    const response = await fetch(url, { signal })
    const json = (await response.json()) as StatusInfo
 
    logger?.verbose('Health check response', json)
 
    if (eq(json.version, newVersion)) {
      logger?.info('Health check passed')
 
      return
    }
 
    throw new Error(`Version mismatch, expected: ${newVersion}, received: ${json.version}`)
  })
}