deploy.js 5.84 KB
/**
 * 分发部署
 * upload >> unzip >> startup
 * 
 * @class Deploy
 * @author jf<jeff.jiang@yoho.cn>
 * @date 2016/5/21
 */

import ssh from 'ssh2';
import path from 'path';

import config from '../../config/config';
import ws from '../../lib/ws';
import {
    DeployInfo,
    Server
} from '../models';

class Deploy {

    constructor(project, building) {
        this.project = project;
        this.building = building;
    }

    async deploy(info) {
        let server = await Server.findByHost(info.host);
        this.server = server;
        this.info = info;
        this.sshDeploy({
            host: server.host,
            username: server.username,
            password: server.password,
            port: server.port
        });
    }

    sshDeploy(serverInfo) {
        console.log('ssh connecting', serverInfo);
        let conn = new ssh.Client();
        let self = this;
        conn.on('ready', async() => {
            console.log(`connected ${serverInfo.host}`);
            try {
                await self._preDeploy(conn);
                await self._scp(conn);
                await self._unzip(conn);
                await self._startup(conn);
                conn.end();
            } catch (e) {
                self._state('fail');
                self._log(e);
            }
        }).on('error', (err) => {
            self._state('fail');
            self._log(err);
        }).connect(serverInfo);
    }

    _preDeploy(conn) {
        let self = this;
        return new Promise((resolve, reject) => {
            let script = `mkdir -p ${self.remoteWorkDir} && mkdir -p ${self.remoteDist}`;
            self._state('preparing');
            self._log(`>>>>>>>>> ${script} >>>>>>>>`);
            conn.exec(script, (err, stream) => {
                if (err) {
                    reject(err);
                } else {
                    stream.on('exit', (code) => {
                        resolve();
                    });
                }

            });
        });
    }

    _scp(conn) {
        let self = this;

        return new Promise((resolve, reject) => {
            self._state('uploading');
            self._log(`>>>> uploading ${self.localFile} ==> ${self.remoteFile}`);
            conn.sftp((err, sftp) => {
                if (err) {
                    reject(err);
                } else {
                    sftp.fastPut(self.localFile, self.remoteFile, {
                        chunkSize: 10240
                    }, (err) => {
                        if (err) {
                            reject(err);
                        } else {
                            self._log('     uploaded success!');
                            self._state('uploaded');
                            resolve();
                        }
                    });
                }
            });
        });
    }

    _unzip(conn) {
        let self = this;
        return new Promise((resolve, reject) => {
            self._state('unziping');
            let script = `tar -zxvf ${self.remoteFile} -C ${self.remoteWorkDir} && rm -rf ${self.remoteDist}`;
            self._log(`>>>> unziping ${self.remoteFile} ==> ${self.remoteWorkDir}`);
            conn.exec(script, (err, stream) => {
                if (err) {
                    reject(err);
                } else {
                    stream.stdout.on('data', (data) => {
                        //self._log(data.toString());
                    });
                    stream.stderr.on('data', (data) => {
                        //self._log(data.toString());
                    });
                    stream.on('exit', (code) => {
                        if (code === 0) {
                            self._state('unziped');
                            resolve();
                        } else {
                            reject('unzip fail: ' + script);
                        }
                    });
                }
            })
        });
    }

    _startup(conn) {
        let self = this;
        let startup = this.project.scripts.start;
        return new Promise((resolve, reject) => {
            self._state('starting');
            self._log(`>>>> ${startup}`);
            conn.exec(`cd ${self.remoteRunningDir} && ${startup}`, (err, stream) => {
                if (err) {
                    reject(err);
                } else {
                    stream.stdout.on('data', (data) => {
                        self._log(data.toString());
                    });
                    stream.stderr.on('data', (data) => {
                        self._log(data.toString());
                    });
                    stream.on('exit', (code) => {
                        if (code === 0) {
                            self._state('running');
                            resolve();
                        } else {
                            reject('startup fail');
                        }
                    });
                }
            });
        });
    }
    
    async _state(state) {
        ws.broadcast(`/deploy/${this.project._id}`, {
            host: this.info.host,
            state: state
        });
        await DeployInfo.updateState(this.info._id, state);
    }

    _log(msg) {
        ws.broadcast(`/deploy/${this.project._id}/log`, {
            host: this.info.host,
            msg: msg
        });
    }

    get remoteWorkDir() {
        return path.join(this.server.deployDir, this.project.name, 'current');
    }

    get remoteRunningDir() {
        return path.join(this.server.deployDir, this.project.name, 'current', this.project.name);
    }

    get remoteDist() {
        return path.join(this.server.deployDir, this.project.name, this.building.buildTime);
    }

    get remoteFile() {
        return path.join(this.server.deployDir, this.building.distFile);
    }

    get localFile() {
        return path.join(config.buildDir, this.building.distFile);
    }
}

export default Deploy;