@devmn/cloud-cli
Version:
CLI tool for Intelligo Cloud.
260 lines (238 loc) • 9.77 kB
text/typescript
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)
}
}
}