UNPKG

@devmn/cloud-cli

Version:

CLI tool for Intelligo Cloud.

260 lines (238 loc) 9.77 kB
import * as fs from 'fs-extra' import * as path from 'path' import { exec, ExecException } from 'child_process' import * as ProgressBar from 'progress' import StdOutUtil from '../utils/StdOutUtil' import SpinnerHelper from '../utils/SpinnerHelper' import IBuildLogs from '../models/IBuildLogs' import { IMachine, IDeployParams } from '../models/storage/StoredObjects' import CliApiManager from '../api/CliApiManager' export default class DeployHelper { private lastLineNumberPrinted = -10000 // we want to show all lines to begin with! constructor(private ssl?: boolean) {} async startDeploy(deployParams: IDeployParams): Promise<boolean> { const appName = deployParams.appName const branchToPush = deployParams.deploySource.branchToPush const tarFilePath = deployParams.deploySource.tarFilePath const imageName = deployParams.deploySource.imageName const machineToDeploy = deployParams.captainMachine if (!appName || !machineToDeploy) { StdOutUtil.printError( "Can't deploy: missing CapRover machine or app name.\n", true ) return false } if ( (branchToPush ? 1 : 0) + (imageName ? 1 : 0) + (tarFilePath ? 1 : 0) !== 1 ) { StdOutUtil.printError( "Can't deploy: only one of branch, tarFile or imageName can be present.\n", true ) return false } let gitHash = '' let tarFileCreatedByCli = false const tarFileNameToDeploy = tarFilePath ? tarFilePath : 'temporary-captain-to-deploy.tar' const tarFileFullPath = tarFileNameToDeploy.startsWith('/') ? tarFileNameToDeploy : path.join(process.cwd(), tarFileNameToDeploy) if (branchToPush) { tarFileCreatedByCli = true StdOutUtil.printMessage(`Saving tar file to: "${tarFileFullPath}"`) gitHash = await this.gitArchiveFile(tarFileFullPath, branchToPush) } StdOutUtil.printMessage( `Deploying ${StdOutUtil.getColoredAppName(appName)} to ${ machineToDeploy.name ? StdOutUtil.getColoredMachineName(machineToDeploy.name) : StdOutUtil.getColoredMachineUrl(machineToDeploy.baseUrl) }...\n` ) try { if (imageName) { await CliApiManager.get( machineToDeploy ).uploadCaptainDefinitionContent( appName, { schemaVersion: 2, imageName }, '', true ) } else { await CliApiManager.get(machineToDeploy).uploadAppData( appName, this.getFileStream(tarFileFullPath), gitHash ) } StdOutUtil.printMessage('Building your source code...\n') if (machineToDeploy.appToken) { StdOutUtil.printMessage( `Deploying ${StdOutUtil.getColoredAppName( appName )} using app token is in progress. Build logs aren't retrieved when using app token.\nWait a few minutes until your app is built and deployed` ) } else { this.startFetchingBuildLogs(machineToDeploy, appName) } return true } catch (e) { throw e } finally { if (tarFileCreatedByCli && fs.pathExistsSync(tarFileFullPath)) { fs.removeSync(tarFileFullPath) } } } private gitArchiveFile(zipFileFullPath: string, branchToPush: string) { return new Promise<string>(function(resolve, reject) { if (fs.pathExistsSync(zipFileFullPath)) { fs.removeSync(zipFileFullPath) } // Removes the temporary file created exec( `git archive --format tar --output "${zipFileFullPath}" ${branchToPush}`, ( error: ExecException | null, stdout1: string, stderr1: string ) => { if (error) { StdOutUtil.printError(`TAR file failed.\n${error}\n`) if (fs.pathExistsSync(zipFileFullPath)) { fs.removeSync(zipFileFullPath) } reject(new Error('TAR file failed')) return } exec( `git rev-parse ${branchToPush}`, ( err: ExecException | null, stdout2: string, stderr2: string ) => { const gitHash = (stdout2 || '').trim() if (err || !/^[a-f0-9]{40}$/.test(gitHash)) { StdOutUtil.printError( `Cannot find hash of last commit on branch "${branchToPush}": ${gitHash}\n${err}\n` ) if (fs.pathExistsSync(zipFileFullPath)) { fs.removeSync(zipFileFullPath) } reject(new Error('rev-parse failed')) return } StdOutUtil.printMessage( `Using last commit on "${branchToPush}": ${gitHash}\n` ) resolve(gitHash) } ) } ) }) } private getFileStream(zipFileFullPath: string) { const fileSize = fs.statSync(zipFileFullPath).size const fileStream = fs.createReadStream(zipFileFullPath) const barOpts = { width: 20, total: fileSize, clear: false } const bar = new ProgressBar( 'Uploading [:bar] :percent (ETA :etas)', barOpts ) fileStream.on('data', chunk => bar.tick(chunk.length)) fileStream.on('end', () => { StdOutUtil.printGreenMessage(`Upload done.\n`) StdOutUtil.printMessage( 'This might take several minutes. PLEASE BE PATIENT...\n' ) }) return fileStream } private async onLogRetrieved( data: IBuildLogs | undefined, machineToDeploy: IMachine, appName: string ) { if (data) { const lines = data.logs.lines const firstLineNumberOfLogs = data.logs.firstLineNumber let firstLinesToPrint = 0 if (firstLineNumberOfLogs > this.lastLineNumberPrinted) { if (firstLineNumberOfLogs < 0) { // This is the very first fetch, probably firstLineNumberOfLogs is around -50 firstLinesToPrint = -firstLineNumberOfLogs } else { StdOutUtil.printMessage('[[ TRUNCATED ]]') } } else { firstLinesToPrint = this.lastLineNumberPrinted - firstLineNumberOfLogs } this.lastLineNumberPrinted = firstLineNumberOfLogs + lines.length for (let i = firstLinesToPrint; i < lines.length; i++) { StdOutUtil.printMessage((lines[i] || '').trim()) } } if (data && !data.isAppBuilding) { if (!data.isBuildFailed) { let appUrl = machineToDeploy.baseUrl .replace('https://', 'http://') .replace('//captain.', `//${appName}.`) if (this.ssl) { appUrl = appUrl.replace('http://', 'https://') } StdOutUtil.printGreenMessage( `\nDeployed successfully ${StdOutUtil.getColoredAppName( appName )}` ) StdOutUtil.printGreenMessage( `App is available at ${StdOutUtil.getColoredMachineUrl( appUrl )}\n`, true ) } else { StdOutUtil.printError( `\nSomething bad happened. Cannot deploy ${StdOutUtil.getColoredAppName( appName )} at ${StdOutUtil.getColoredMachineName( machineToDeploy.name || machineToDeploy.baseUrl )}.\n`, true ) } } else { setTimeout( () => this.startFetchingBuildLogs(machineToDeploy, appName), 2000 ) } } private async startFetchingBuildLogs( machineToDeploy: IMachine, appName: string ) { try { const data = await CliApiManager.get( machineToDeploy ).fetchBuildLogs(appName) this.onLogRetrieved(data, machineToDeploy, appName) } catch (error) { StdOutUtil.printError( `\nSomething bad happened while retrieving ${StdOutUtil.getColoredAppName( appName )} app build logs.\n${error.message || error}\n` ) this.onLogRetrieved(undefined, machineToDeploy, appName) } } }