import * as chalk from 'chalk'; import {command, help, namespace, option, param, required} from 'oo-cli'; import {die} from '../../lib/die'; import {formatError} from '../../lib/formatError'; import {handleInterrupt} from '../../lib/handleInterrupt'; import {TerminalSpinner} from '../../lib/TerminalSpinner'; import {terminal} from 'terminal-kit'; import {Rivendell} from '../../lib/Rivendell'; @namespace('directory') export class UpgradeCommand { @param @help('The App ID of the app to upgrade (e.g. my_app)') public appId!: string; @param @help('The Tracker ID of the account to install into') public trackerId!: string; @option('v') @required @help('The desired version (i.e. 1.0.0)') public toVersion!: string; @option('a') @help('The availability zone that will be targeted (default: us)') private availability: string = ''; private STATUS_CHECK_ERROR_THRESHOLD = 5; @command @help('Upgrade the Install to a specific app version') public async upgrade() { handleInterrupt(); try { const shard = this.availability || 'us'; const install = await Rivendell.fetchAppInstallation(this.appId, this.trackerId, shard); if (install.version === this.toVersion) { console.log( chalk.yellow(`${this.trackerId} is currently at ${this.appId}@${install.version} in ${shard}.`) ); } else { const result = await Rivendell.upgrade(install.id, this.appId, this.toVersion, shard); console.log(`Upgrading ${this.appId} for ${this.trackerId} to ${this.toVersion} in ${shard}`); await this.watchForCompletion(shard, result.id); console.log( chalk.green(`Upgraded ${this.trackerId} to ${this.appId}@${this.toVersion} in ${shard}.`)); terminal.removeAllListeners('key'); } } catch (e: any) { die(formatError(e)); } } private watchForCompletion(shard: string, upgradeId: number) { console.log(chalk.gray(`Upgrade was scheduled in ${shard}, watching for completion... CTRL+C to stop checking.`)); return new Promise((resolve, reject) => { let attempts = 0; let errorCount = 0; const spinner = new TerminalSpinner().start(''); const checkUpgrade = () => { attempts++; Rivendell.fetchUpgrade(upgradeId, shard) .then((upgrade) => { spinner.update(`Upgrade status: ${upgrade.status.toString().replace('_', ' ').toLowerCase()}`); if (upgrade.status === Rivendell.UpgradeStatus.COMPLETE) { clearInterval(interval); spinner.stop(); resolve(); } else { if (upgrade.status === Rivendell.UpgradeStatus.FINALIZING_LIFECYCLE_FAILED.toString() || upgrade.status === Rivendell.UpgradeStatus.UPDATING_WEBHOOKS_FAILED.toString() || upgrade.status === Rivendell.UpgradeStatus.UPGRADING_LIFECYCLE_FAILED.toString()) { clearInterval(interval); spinner.stop(); die(`Upgrade failed with status ${upgrade.status}: ${upgrade.message}`); } if (attempts > 300) { clearInterval(interval); spinner.stop(); die('Timed out waiting for completion'); } } errorCount = 0; }) .catch((e) => { errorCount++; if (errorCount >= this.STATUS_CHECK_ERROR_THRESHOLD) { clearInterval(interval); spinner.stop(); reject(e); } }); }; const interval = setInterval(checkUpgrade, 2000); checkUpgrade(); }); } }