1 | var spawn = require('child_process').spawn;
|
2 | var exec = require('child_process').exec;
|
3 | var util = require('util');
|
4 | var helpers = require('./helpers');
|
5 | var fs = require('fs');
|
6 | var ejs = require('ejs');
|
7 | var path = require('path');
|
8 | var debug = require('debug');
|
9 |
|
10 | function 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 |
|
33 | Session.prototype.setTimeout = function(timeoutMillis) {
|
34 | this._debug('set timeout: %d', timeoutMillis);
|
35 | this._timeout = timeoutMillis;
|
36 | };
|
37 |
|
38 | Session.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 |
|
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 |
|
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 |
|
124 | callback.apply(null, args);
|
125 | }
|
126 | }
|
127 | };
|
128 |
|
129 | Session.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 |
|
183 | callback.apply(err, args);
|
184 | }
|
185 | }
|
186 | };
|
187 |
|
188 | Session.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 |
|
204 | Session.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 |
|
219 | Session.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 |
|
262 | bash.stdout.removeListener('data', onStdOut);
|
263 | bash.stderr.removeListener('data', onStdErr);
|
264 | bash.removeListener('error', sendCallback);
|
265 | bash.removeListener('close', onClose);
|
266 |
|
267 |
|
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 |
|
300 | module.exports = Session
|