1 |
|
2 |
|
3 |
|
4 |
|
5 | 'use strict';
|
6 |
|
7 | const util = require('../utils'),
|
8 | extend = require('extend'),
|
9 | SSH2 = require('ssh2'),
|
10 | EventEmitter = require('events').EventEmitter,
|
11 | LoadingORA = require('../loadProgress').LoadingORA,
|
12 | dateFormat = require('dateformat'),
|
13 | Uploader = require('./uploader'),
|
14 | Backup = require('./backup'),
|
15 | T = require('../tools');
|
16 |
|
17 | class Deploy extends EventEmitter {
|
18 | constructor(config) {
|
19 | super();
|
20 | this.basePath = T.getArg('cwdir') || process.cwd();
|
21 |
|
22 | this.host = null;
|
23 | this.port = 22;
|
24 | this.username = 'anonymous';
|
25 | this.password = null;
|
26 | this.timeout = 50000;
|
27 |
|
28 |
|
29 | this.localPath = '';
|
30 | this.filters = [];
|
31 |
|
32 |
|
33 | this.remotePath = null;
|
34 | this.platform = 'unix';
|
35 | this.onUploadedComplete = null;
|
36 | this.onUploadedFileSuccess = null;
|
37 | this.onUploadedFileError = null;
|
38 |
|
39 | this.isExecute = true;
|
40 |
|
41 |
|
42 | this.isBuildAfter = true;
|
43 |
|
44 | this.type = 'full';
|
45 |
|
46 | this.log = 'progress';
|
47 |
|
48 | this.logFile = null;
|
49 |
|
50 | this.connectType = 'sftp';
|
51 |
|
52 | this.agent = null;
|
53 | this.agentForward = false;
|
54 |
|
55 | this.privateKey = null;
|
56 | this.passphrase = null;
|
57 |
|
58 | this.keepaliveCountMax = 3;
|
59 |
|
60 | this.authKey = '';
|
61 | this.auth = null;
|
62 | this.authFile = '.ftppass';
|
63 |
|
64 | this.key = {};
|
65 | this.keyLocation = null;
|
66 | this.keyContents = null;
|
67 |
|
68 | this.uploader = null;
|
69 | this.isRollback = true;
|
70 |
|
71 | this.sftp = null;
|
72 | this.ssh2 = null;
|
73 |
|
74 | this.options = config;
|
75 |
|
76 | if (util.isObject(config)) {
|
77 | extend(true, this, config);
|
78 | }
|
79 |
|
80 | this.init();
|
81 | this.loading = new LoadingORA();
|
82 | this.uploader = new Uploader(this.localPath, this.remotePath, this._getConnectOptions());
|
83 | }
|
84 |
|
85 | init() {
|
86 |
|
87 | this.initType();
|
88 |
|
89 |
|
90 | this.initPath();
|
91 |
|
92 |
|
93 | this.initRemotePath();
|
94 |
|
95 |
|
96 | this.initFilters();
|
97 |
|
98 |
|
99 | this.initAuth();
|
100 |
|
101 |
|
102 | this.initPresentKey();
|
103 |
|
104 |
|
105 | this.initLog();
|
106 |
|
107 |
|
108 | this.initTimeout();
|
109 | }
|
110 |
|
111 | start() {
|
112 |
|
113 | this.isExecute ? this._initUploader() : this.stop();
|
114 | return this;
|
115 | }
|
116 |
|
117 | stop() {
|
118 | this.uploader && this.uploader.stop();
|
119 |
|
120 | }
|
121 |
|
122 | initType() {
|
123 |
|
124 | this._isIncrementType = this.type === 'increment';
|
125 |
|
126 | this._isFullType = this.type === 'full';
|
127 | }
|
128 |
|
129 | initLog() {
|
130 | this._isProgressLog = this.log === 'progress';
|
131 | }
|
132 |
|
133 | initTimeout() {
|
134 |
|
135 | if (this.connectType === 'ftp') {
|
136 | this.connTimeout = this.pasvTimeout = this.keepalive = this.timeout;
|
137 | }
|
138 | }
|
139 |
|
140 |
|
141 | initPath() {
|
142 | if (util.isString(this.localPath) && this.localPath.length > 0) {
|
143 | this.localPath = (!T.isAbsolutePath(this.localPath) && [T.Path.resolve(process.cwd(), this.localPath)]) || [this.localPath];
|
144 | } else if (util.isArray(this.localPath)) {
|
145 | this.localPath = this.localPath
|
146 | .filter(p => {
|
147 | return p && util.isString(p);
|
148 | })
|
149 | .map(p => {
|
150 | if (p && p.length > 0) {
|
151 | return !T.isAbsolutePath(p) ? T.Path.resolve(process.cwd(), p) : p;
|
152 | }
|
153 | });
|
154 | } else {
|
155 | T.log.error('× LocalPath Error: localPath is not found ( localPath can be String or Array => vinyl-fs module )');
|
156 | }
|
157 | }
|
158 |
|
159 | initRemotePath() {
|
160 | if (!this.remotePath) {
|
161 | T.log.error('× RemotePath Error: remotePath is not found ( remotePath can be String )');
|
162 | }
|
163 | }
|
164 |
|
165 |
|
166 | initFilters() {
|
167 | if (this.filters && this.filters.length > 0) {
|
168 | this.filters = this.filters.map(f => {
|
169 | f = `!${f}`;
|
170 | this.localPath.push(f);
|
171 | return f;
|
172 | });
|
173 | }
|
174 | }
|
175 |
|
176 |
|
177 | initAuth() {
|
178 | if (util.isObject(this.auth)) {
|
179 | this.auth['key'] && (this.authFile = this.auth['key']);
|
180 | this.auth['file'] && (this.authFile = this.auth['file']);
|
181 | }
|
182 | let authFile = T.Path.join(__dirname, this.authFile);
|
183 | if (this.authKey && T.fs.existsSync(authFile)) {
|
184 | let auth = JSON.parse(T.fs.readFileSync(authFile, 'utf8'))[this.authKey];
|
185 | if (!auth) this.emit('error', new Error('Could not find authkey in .ftppass'));
|
186 | if (typeof auth === 'string' && auth.indexOf(':') !== -1) {
|
187 | let authparts = auth.split(':');
|
188 | auth = { user: authparts[0], pass: authparts[1] };
|
189 | }
|
190 | this.user = auth.user;
|
191 | this.pass = auth.pass;
|
192 | }
|
193 |
|
194 | this.password = this.pass;
|
195 | this.username = this.user;
|
196 | }
|
197 |
|
198 |
|
199 | initPresentKey() {
|
200 | let key = this.key || this.keyLocation || null;
|
201 | if (key && typeof key === 'string') key = { location: key };
|
202 |
|
203 |
|
204 | if (!key && (this.passphrase || this.keyContents || !this.password)) {
|
205 | key = {};
|
206 | }
|
207 |
|
208 | if (key) {
|
209 |
|
210 | key.contents = key.contents || this.keyContents;
|
211 | key.passphrase = key.passphrase || this.passphrase;
|
212 |
|
213 |
|
214 | key.location = key.location || ['~/.ssh/id_rsa', '/.ssh/id_rsa', '~/.ssh/id_dsa', '/.ssh/id_dsa'];
|
215 |
|
216 |
|
217 | if (!util.isArray(key.location)) key.location = [key.location];
|
218 |
|
219 |
|
220 | if (key.location) {
|
221 | let home = process.env.HOME || process.env.USERPROFILE;
|
222 | for (let i = 0; i < key.location.length; ++i) if (key.location[i].substr(0, 2) === '~/') key.location[i] = T.Path.resolve(home, key.location[i].replace(/^~\//, ''));
|
223 |
|
224 | for (let i = 0, keyPath; (keyPath = key.location[i++]); ) {
|
225 | if (T.fs.existsSync(keyPath)) {
|
226 | key.contents = T.fs.readFileSync(keyPath);
|
227 | break;
|
228 | }
|
229 | }
|
230 | } else if (!key.contents) {
|
231 | this.emit('error', new Error(`Cannot find RSA key, searched: ${key.location.join(', ')} `));
|
232 | }
|
233 | }
|
234 | this.key = key;
|
235 | if (this.key && this.key.contents) {
|
236 | this.keyContents = this.key.contents;
|
237 | this.privateKey = this.keyContents;
|
238 | this.passphrase = this.key.passphrase || this.passphrase;
|
239 | }
|
240 | }
|
241 |
|
242 |
|
243 | _initUploader() {
|
244 | this.uploader.on('start', this._onStart.bind(this));
|
245 | this.uploader.on('uploaded', this._onUploaded.bind(this));
|
246 | this.uploader.on('done', this._onDone.bind(this));
|
247 | this.uploader.setType(this.type);
|
248 | this.uploader.start();
|
249 | }
|
250 |
|
251 | _onStart({ message }) {
|
252 | this._writeLogFile(`\n-------------Start Log [${dateFormat(Date.now(), 'yyyy-mm-dd')}]-------------\n`);
|
253 | T.log.yellow(this._startText());
|
254 | this._writeLogFile(this._startText());
|
255 | message && T.log.gray(message);
|
256 | message && this._writeLogFile(message);
|
257 | this._isProgressLog && this.loading.start(T.msg.green(this._startText()));
|
258 | }
|
259 |
|
260 | _onUploaded(payload) {
|
261 | const { file, realPath, size, error, message } = payload;
|
262 | if (error) {
|
263 | this._writeLogFile(message);
|
264 | return this._isProgressLog ? this.loading.text(message) : T.log.green(T.msg.red(message));
|
265 | }
|
266 | this._writeLogFile(message);
|
267 | util.isFunction(this.onUploadedFileSuccess) ? this.onUploadedFileSuccess.call(this, { file, realPath, size }) : this._isProgressLog ? this.loading.text(message) : T.log.green(message);
|
268 | }
|
269 |
|
270 | _onDone(payload) {
|
271 | this._isProgressLog && this.loading.stop();
|
272 | const { fileCount, modCount, status, duration, uploader } = payload;
|
273 | let iText = this._isIncrementType ? `√ [${this.host}] Deploy Status: ` + T.msg.cyan(status) : '';
|
274 | let dText = iText + T.msg.green(`√ [${this.host}] ${this._isFullType ? fileCount + ' ' : ''}files uploaded OK =^_^= (^_^) =^_^= !!!`);
|
275 | util.isFunction(this.onUploadedComplete) ? this.onUploadedComplete.call(this) : T.log.green(dText);
|
276 | this._writeLogFile(dText);
|
277 | this.uploader.stop();
|
278 | T.log.yellow(this._stopText(duration));
|
279 | this._writeLogFile(this._stopText(duration));
|
280 | this._writeLogFile(`-------------End Log-------------\n`);
|
281 | this.emit('deploy_done', { deploy: this });
|
282 | }
|
283 |
|
284 |
|
285 | _getConnectOptions() {
|
286 | let options = {
|
287 | host: this.host,
|
288 | port: this.port,
|
289 | username: this.username
|
290 | };
|
291 |
|
292 | if (this.password) {
|
293 | options.password = this.password;
|
294 | } else if (this.agent) {
|
295 | options.agent = this.agent;
|
296 | options.agentForward = this.agentForward || false;
|
297 | } else if (this.privateKey && this.passphrase) {
|
298 | options.privateKey = this.privateKey;
|
299 | options.passphrase = this.passphrase;
|
300 | }
|
301 | options.readyTimeout = this.timeout;
|
302 | options.platform = this.platform;
|
303 | options.keepaliveCountMax = this.keepaliveCountMax;
|
304 | return options;
|
305 | }
|
306 |
|
307 | _startText() {
|
308 | return `→ [${this.host}] Deploy start ...... `;
|
309 | }
|
310 | _stopText(duration) {
|
311 | return `√ [${this.host}] Deploy done =^_^= (^_^) =^_^= !!!, after ${T.msg.yellow((duration / 1000).toFixed(2) + ' s')} \n`;
|
312 | }
|
313 | _uploadedText(realPath, size) {
|
314 | return `√ [${T.getTime()}] uploaded '${realPath}', ${T.msg.yellow(size / 1000 + ' kb')}`;
|
315 | }
|
316 | _writeLogFile(txt) {
|
317 | txt = txt.replace(/(()?\[\d{2}m)/g, '') + '\n';
|
318 | if (!this.logFile || !util.isString(this.logFile)) return false;
|
319 | if (!T.fs.existsSync(this.logFile)) {
|
320 | T.fs.writeFileSync(this.logFile, txt, 'utf8');
|
321 | } else {
|
322 | T.fs.appendFileSync(this.logFile, txt, 'utf8');
|
323 | }
|
324 | }
|
325 | }
|
326 |
|
327 | module.exports = Deploy;
|