UNPKG

7.23 kBJavaScriptView Raw
1var spawn = require('child_process').spawn;
2var exec = require('child_process').exec;
3var util = require('util');
4var helpers = require('./helpers');
5var fs = require('fs');
6var ejs = require('ejs');
7var path = require('path');
8var debug = require('debug');
9
10function Session(host, auth, options) {
11 if(!(this instanceof Session)) {
12 return new Session(host, auth, options);
13 }
14
15 var self = this;
16 this._host = host;
17 this._auth = auth;
18 this._options = options || {};
19
20 this._tasks = [];
21 this._callbacks = [];
22
23 this._timeout = this._options.timeout;
24
25 this._sshOptions = '';
26 if (!this._options.ssh) this._options.ssh = {};
27 Object.keys(this._options.ssh).forEach(function (key) {
28 self._sshOptions += util.format(' -o %s=%s', key, self._options.ssh[key]);
29 });
30 this._debug = debug('nodemiral:sess:' + host);
31}
32
33Session.prototype.setTimeout = function(timeoutMillis) {
34 this._debug('set timeout: %d', timeoutMillis);
35 this._timeout = timeoutMillis;
36};
37
38Session.prototype.copy = function(src, dest, vars, callback) {
39 var self = this;
40 var command;
41 var copyFile = src;
42 var tmpFile;
43 var pemFile;
44
45 this._debug('copy file - src: %s, dest: %s, vars: %j', src, dest, vars);
46
47 if(typeof(vars) == 'function') {
48 callback = vars;
49 vars = null;
50 }
51
52 //lets do templating
53 if(vars) {
54 copyFile = tmpFile = '/tmp/' + helpers.randomId();
55 }
56
57 if(this._auth.pem) {
58 pemFile = '/tmp/' + helpers.randomId();
59 fs.writeFile(pemFile, this._auth.pem, { mode: '0400' }, afterPemFileWritten);
60 } else if(this._auth.password) {
61 command = util.format('sshpass -p %s scp%s %s %s@%s:%s',
62 this._auth.password, self._sshOptions, copyFile, this._auth.username, this._host, dest);
63 startProcessing();
64 } else {
65 throw new Error('NO_PASSWORD_OR_PEM');
66 }
67
68 function afterPemFileWritten(err) {
69 if(err) {
70 callback(err);
71 } else {
72 command = util.format('scp%s -i %s %s %s@%s:%s',
73 self._sshOptions, pemFile, copyFile, self._auth.username, self._host, dest);
74 startProcessing();
75 }
76 }
77
78 function startProcessing() {
79 if(vars) {
80 //do templating
81 self._applyTemplate(src, vars, afterTemplateApplied)
82 } else {
83 self._doSpawn(command, afterCompleted);
84 }
85 }
86
87 function afterTemplateApplied(err, content) {
88 if(err) {
89 callback(err);
90 } else {
91 fs.writeFile(tmpFile, content, afterFileWrittern);
92 }
93 }
94
95 function afterFileWrittern(err) {
96 if(err) {
97 callback(err);
98 } else {
99 self._doSpawn(command, afterCompleted);
100 }
101 }
102
103 function afterCompleted() {
104 var args = arguments;
105 deletePemFile();
106
107 function deletePemFile() {
108 if(pemFile) {
109 fs.unlink(pemFile, deleteTmpFile);
110 } else {
111 deleteTmpFile();
112 }
113 }
114 function deleteTmpFile() {
115 if(tmpFile) {
116 fs.unlink(tmpFile, sendCallback);
117 } else {
118 sendCallback();
119 }
120 }
121
122 function sendCallback() {
123 //unlink error should not throw any errors
124 callback.apply(null, args);
125 }
126 }
127};
128
129Session.prototype.execute = function(shellCommand, callback) {
130 var self = this;
131 var tmpScript = '/tmp/' + helpers.randomId();
132 var command;
133 var pemFile;
134
135 this._debug('execute - command: %s', shellCommand);
136
137 if(this._auth.pem) {
138 pemFile = '/tmp/' + helpers.randomId();
139 fs.writeFile(pemFile, this._auth.pem, { mode: '0400' }, afterPemFileWritten);
140 } else if(this._auth.password) {
141 command = util.format('sshpass -p %s ssh%s %s@%s "bash -s" < %s',
142 this._auth.password, self._sshOptions, this._auth.username, this._host, tmpScript);
143 startProcessing();
144 } else {
145 throw new Error('NO_PASSWORD_OR_PEM');
146 }
147
148 function afterPemFileWritten(err) {
149 if(err) {
150 callback(err);
151 } else {
152 command = util.format('ssh%s -i %s %s@%s "bash -s" < %s',
153 self._sshOptions, pemFile, self._auth.username, self._host, tmpScript);
154 startProcessing();
155 }
156 }
157
158 function startProcessing() {
159 fs.writeFile(tmpScript, shellCommand, function(err) {
160 if(err) {
161 callback(err);
162 } else {
163 self._doSpawn(command, afterCompleted);
164 }
165 });
166 }
167
168 function afterCompleted() {
169 var args = arguments;
170
171 if(pemFile) {
172 fs.unlink(pemFile, removeTmpScript);
173 } else {
174 removeTmpScript();
175 }
176
177 function removeTmpScript() {
178 fs.unlink(tmpScript, sendCallback);
179 }
180
181 function sendCallback(err) {
182 //unlink error should not throw any errors
183 callback.apply(err, args);
184 }
185 }
186};
187
188Session.prototype.executeScript = function(scriptFile, vars, callback) {
189 var self = this;
190 if(typeof(vars) == 'function') {
191 callback = vars;
192 vars = null
193 }
194
195 this._applyTemplate(scriptFile, vars, function(err, content) {
196 if(err) {
197 callback(err);
198 } else {
199 self.execute(content, callback);
200 }
201 });
202};
203
204Session.prototype._applyTemplate = function(file, vars, callback) {
205 var self = this;
206 fs.readFile(file, {encoding: 'utf8'}, function(err, content) {
207 if(err) {
208 callback(err);
209 } else {
210 if(vars) {
211 var ejsOptions = self._options.ejs || {};
212 var content = ejs.compile(content, ejsOptions)(vars);
213 }
214 callback(null, content);
215 }
216 });
217};
218
219Session.prototype._doSpawn = function(command, callback) {
220 var self = this;
221 var tmpScript = '/tmp/' + helpers.randomId();
222 var logs = { stdout: "", stderr: ""};
223 var bash;
224 var time
225 var timeoutHandler;
226
227 this._debug('spawning command- %s', command);
228
229 fs.writeFile(tmpScript, command, function(err) {
230 if(err) {
231 callback(err);
232 } else {
233 executeTmpScript();
234 }
235 })
236
237 function executeTmpScript() {
238 bash = spawn('bash', [tmpScript]);
239
240 bash.stdout.on('data', onStdOut);
241 bash.stderr.on('data', onStdErr);
242 bash.once('error', sendCallback);
243 bash.once('close', onClose);
244
245 if(self._timeout) {
246 timeoutHandler = setTimeout(onTimeout, self._timeout);
247 }
248 }
249
250 function sendCallback(err, code, logs) {
251 if(err) {
252 self._debug('error: %s', err.message);
253 } else {
254 self._debug('spawn completed - code: %d - \n\tstdout: %s \t\tstderr', code, logs.stdout, logs.stderr);
255 }
256
257 if(callback) {
258 callback(err, code, logs);
259 callback = null;
260
261 //cleanup
262 bash.stdout.removeListener('data', onStdOut);
263 bash.stderr.removeListener('data', onStdErr);
264 bash.removeListener('error', sendCallback);
265 bash.removeListener('close', onClose);
266
267 //clenup tmpScript
268 fs.unlink(tmpScript);
269 }
270 }
271
272 function onClose(code) {
273 if(timeoutHandler) {
274 clearTimeout(timeoutHandler);
275 timeoutHandler = null;
276 }
277 sendCallback(null, code, logs);
278 }
279
280 function onStdOut(data) {
281 logs.stdout += data.toString();
282 }
283
284 function onStdErr(data) {
285 logs.stderr += data.toString();
286 }
287
288 function onTimeout() {
289 var killScript = path.resolve(__dirname, '../scripts/kill.sh');
290 sendCallback(new Error('TIMEOUT'));
291 exec('sh ' + killScript + ' ' + bash.pid, function(err) {
292 if(err) {
293 console.error('kiiling on timeout failed');
294 }
295 });
296 timeoutHandler = null;
297 }
298}
299
300module.exports = Session