forked from tsmith/node-control
-
Notifications
You must be signed in to change notification settings - Fork 0
/
README
401 lines (254 loc) · 10.9 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
DESCRIPTION
Use node-control to define ssh and scp tasks for system administration and code
deployment and execute them on one or more machines simultaneously. The
implementation and API is completely asynchronous, allowing coding of tasks
according to Node conventions and control of many machines in parallel.
Strong logging creates a complete audit trail of commands executed on remote
machines in logs easily analyzed by standard text manipulation tools.
The only dependency is OpenSSH and Node on the local control machine. Remote
machines simply need to have a standard sshd daemon running.
QUICK EXAMPLE
To get the current date from the three machines listed in the 'mycluster'
config task as the 'mylogin' user with a single command:
var control = require('./node-control'),
task = control.task;
task('mycluster', 'Config for my cluster', function () {
var config, addresses;
config = {
user: 'mylogin'
};
addresses = [ 'a.mydomain.com',
'b.mydomain.com',
'c.mydomain.com' ];
return control.hosts(config, addresses);
});
task('date', 'Get date', function (host) {
host.ssh('date');
});
control.begin();
If saved in a file named 'mycontroller.js', run with:
node mycontroller.js mycluster date
Each machine is contacted in parallel, date is executed, and the output from
the remote machine is printed to the console. Example output:
Performing mycluster
Performing date for a.mydomain.com
a.mydomain.com:mylogin:ssh: date
Performing date for b.mydomain.com
a.mydomain.com:mylogin:ssh: date
Performing date for c.mydomain.com
a.mydomain.com:mylogin:ssh: date
a.mydomain.com:stdout: Sun Jul 18 13:30:50 UTC 2010
a.mydomain.com:exit: 0
b.mydomain.com:stdout: Sun Jul 18 13:30:50 UTC 2010
c.mydomain.com:stdout: Sun Jul 18 13:30:50 UTC 2010
b.mydomain.com:exit: 0
c.mydomain.com:exit: 0
Each line of output is labeled with the address of the host the command was
executed on. The actual command sent and the user used to send it is
displayed. stdout and stderr output of the remote process is identified
as well as the final exit code of the local ssh command. Each line also appears
timestamped in a hosts.log file in the current working directory.
CODE DEPLOYMENT EXAMPLE
A task that will upload a local zip file containing a release of a node
application to a remote machine, unzip it, and start the node application.
var path = require('path');
task('deploy', 'Deploy my app', function (host, release) {
var basename = path.basename(release),
remoteDir = '/apps/',
remotePath = path.join(remoteDir, basename),
remoteAppDir = path.join(remoteDir, 'myapp');
host.scp(release, remoteDir, function () {
host.ssh('tar xzvf ' + remotePath + ' -C ' + remoteDir, function () {
host.ssh("sh -c 'cd " + remoteAppDir + " && node myapp.js'");
});
});
});
Execute as follows, for example:
node mycontroller.js mycluster deploy ~/myapp/releases/myapp-1.0.tgz
A full deployment solution would deal with shutting down the existing
application and have different directory conventions. node-control does not
assume a particular style or framework, but provides tools to build a
custom deployment strategy for your application or framework.
INSTALLATION
Clone this repository with git or download the latest version using the GitHub
repository Downloads link. Then use as a standard Node module by requiring the
node-control directory.
If you use npm:
npm install control
If you install the npm package, use this require statement:
var control = require('control');
CONFIG TASKS
In node-control, you always call two tasks on the command line when doing
remote operations. The first task is the config task, which must return an
array of host objects. Each host object represents a single host and has its
own set of properties. The hosts() method exported by control.js allows you to
take a single object literal config and multiply it by an array of addresses to
get a list of host objects that all (prototypically) inherit from the same
config:
task('mycluster', 'Config for my cluster', function () {
var config, addresses;
config = {
user: 'mylogin'
};
addresses = [ 'a.mydomain.com',
'b.mydomain.com',
'c.mydomain.com' ];
return control.hosts(config, addresses);
});
Config tasks enable definition of reusable work tasks independent of the
machines they will control. For example, if you have a staging environment with
different machines than your production environment you can create two
different config tasks returning different hosts, yet use the same deploy task:
node mycontroller.js stage deploy ~/myapp/releases/myapp-1.0.tgz
...
node mycontroller.js production deploy ~/myapp/releases/myapp-1.0.tgz
HOST OBJECTS
Each host object returned by the config task is independently passed to the
second task, which is the work task ('deploy' in the above example). The host
object is always passed to the work task's function as the first
argument:
task('date', 'Get date', function (host) {
The host object allows access to all the properties defined in the config and
also provides the ssh and scp methods for communicating with the remote
machine.
The ssh method takes one argument - the command to be executed on the
remote machine. The scp method takes two arguments - the local file path and the
remote file path.
Both ssh and scp methods are asynchronous and can additionally take a callback
function that is executed once the ssh or scp operation is complete. This
guarantees that the first operation completes before the next one begins on
that host:
host.scp(release, remoteDir, function () {
host.ssh('tar xzvf ' + remotePath + ' -C ' + remoteDir, function () {
Callbacks can be chained as far as necessary.
ARGUMENTS
Arguments on the command line after the name of the work task become arguments
to the work task's function:
task('deploy', 'Deploy my app', function (host, release) {
This command:
node mycontroller.js stage deploy ~/myapp/releases/myapp-1.0.tgz
Results in release = '~/myapp/releases/myapp-1.0.tgz'.
More than one argument is possible:
task('deploy', 'Deploy my app', function (host, release, tag) {
BEGIN
To execute the tasks using a tasks file, use the begin() method at the
bottom of the tasks file:
var control = require('./node-control');
... // Define tasks
control.begin();
begin() calls the first (config) task identified on the command line to get the
array of host objects, then calls the second (work) task with each of the host
objects. From that point, everything happens asynchronously as all hosts work
their way through the work task.
PERFORMING MULTIPLE TASKS
A task can call other tasks using perform() and optionally pass arguments to
them:
var perform = require('./node-control').perform;
...
task('mytask', 'My task description', function (host, argument) {
perform('anothertask', host, argument);
perform() requires only the task name and the host object. Arguments are
optional. If the other task supports it, optionally pass a callback function as
one of the arguments:
perform('anothertask', host, function () {
Tasks that support asynchronous performance should call the callback function
when done doing their own work. For example:
task('anothertask', 'My other task description', function (host, callback) {
host.ssh('date', function () {
if (callback) {
callback();
}
});
});
The peform() call can occur anywhere in a task, not just at the beginning.
LIST TASKS
To list all defined tasks with descriptions:
node mycontroller.js mycluster list
NAMESPACES
Use a colon, dash, or similar convention when naming if you want to group tasks
by name. For example:
task('bootstrap:tools', 'Bootstrap tools', function (host) {
...
task('bootstrap:compilers', 'Bootstrap compilers', function (host) {
SUDO
To use sudo, just include sudo as part of your command:
host.ssh('sudo date');
This requires that sudo be installed on the remote machine and have requisite
permissions setup.
ROLES
Some other frameworks like Vlad and Capistrano provide the notion of roles for
different hosts. node-control does not employ a separate roles construct. Since
hosts can have any properties defined on them in a config task, a possible
pattern for roles if needed:
task('mycluster', 'Config for my cluster', function () {
var appConfig, dbConfig, dbs, apps;
dbConfig = {
user: 'dbuser',
role: 'db'
};
dbs = control.hosts(dbConfig, ['db1.mydomain.com', 'db2.mydomain.com']);
appConfig = {
user: 'appuser',
role: 'app'
};
apps = control.hosts(appConfig, ['app1.mydomain.com', 'app2.mydomain.com']);
return dbs.concat(apps);
});
task('deploy', 'Deploy my app', function (host, release) {
if (host.role === 'db') {
// Do db deploy work
}
if (host.role === 'app') {
// Do app deploy work
}
});
LOGS
All commands sent and responses received are logged with timestamps (from the
control machine's clock). By default, logging goes to a hosts.log file in the
working directory of the node process. However, you can override this in your
control script:
task('mycluster', 'Config for my cluster', function () {
var config, addresses;
config = {
user: 'mylogin',
log: '~/mycluster-control.log'
};
addresses = [ 'myhost1.mydomain.com',
'myhost2.mydomain.com',
'myhost3.mydomain.com' ];
return control.hosts(config, addresses);
});
Since each host gets its own log property, every host could conceivably have
its own log fie. However, every line in the log file has a prefix that includes
the host address so, for example:
grep myhost1.mydomain.com hosts.log | less
Would allow paging the log and seeing only lines pertaining to
myhost1.mydomain.com.
If you send something you do not want to get logged (like a password) in a
command, use the log mask:
host.logMask = secret;
host.ssh('echo ' + secret + ' > file.txt');
The console and command log file will show the masked text as asterisks instead
of the actual text.
SSH
To avoid repeatedly entering passwords across possibly many hosts, use standard
ssh keypair authentication.
Each host.ssh() call requires a new connection to the host. To configure ssh to
reuse a single connection, place this:
Host *
ControlMaster auto
ControlPath ~/.ssh/master-%r@%h:%p
In your ssh config file (create if it does not exist):
~/.ssh/config
To pass options to the ssh command when using ssh(), add the option or options
as an array to the sshOptions property of the config, or concat the option or
options to the host object sshOptions property prior to executing the command:
// Config task
config = {
user: 'mylogin',
sshOptions = [ '-2', '-p 44' ]
};
// Work task
host.sshOptions = host.sshOptions.concat('-v');
FEEDBACK
Feedback welcome at [email protected] or the Node mailing list.